Der Community Day für Java Developers bietet am 16. Oktober Expertenvorträgen und Demos, die sich auf Java und Open Source konzentrieren.
Der iJUG e.V. [1] (Interessenverbund der Java User Groups) hat sich mit der Eclipse Foundation [2] zusammengetan, um im Rahmen der EclipseCon am 16. Oktober ein Tagesevent speziell für die (deutschsprachige) Java Community zu gestalten. Hier gibt es einen ganzen Tag voll Java Themen zu einem fairen Preis.
Der Community Day für Java Developers [3] auf der EclipseCon [4] ist ein ganztägiges Programm mit Expertenvorträgen, Demos und anregenden Sessions, die sich auf Java und die Entwicklung von Unternehmensanwendungen unter Verwendung von quelloffenen, herstellerunabhängigen Prozessen und Technologien konzentrieren. Die Open-Source-Projekte Jakarta EE [5], Adoptium [6] und MicroProfile [7] sowie andere von der Eclipse Foundation gehostete Java-Projekte und -Communities werden von weltweit führenden Java-Innovatoren wie Azul [8], Google [9], IBM [10], Microsoft [11], Open Elements [12], Oracle [13], Red Hat [14] und Tomitribe [15] unterstützt.
Mit 14 technischen Sessions, Networking und Verpflegung kostet das eintägige Event weniger als 50 Euro. Das wird durch die Zusammenarbeit zwischen der Eclipse Foundation, dem iJUG e.V. und den Sponsoren der EclipseCon ermöglicht.
Die Veranstaltung ist eine gute Gelegenheit, um herauszufinden, wie andere diese Technologien nutzen, sich mit Expertinnen und Experten aus der Community zu treffen, die wichtigsten Aspekte der Jakarta EE-, Adoptium- und MicroProfile-Technologien besser zu verstehen und Ideen mit innovativen Personen und Unternehmen des Ökosystems zu teilen.
URL dieses Artikels:
https://www.heise.de/-9192048
Links in diesem Artikel:
[1] https://www.ijug.eu/
[2] https://www.eclipse.org/
[3] https://www.eclipsecon.org/2023/java-community-day
[4] https://www.eclipsecon.org/2023
[5] https://jakarta.ee/
[6] https://adoptium.net/
[7] https://microprofile.io/
[8] https://www.azul.com/
[9] https://about.google/
[10] https://www.ibm.com/
[11] https://www.microsoft.com/
[12] https://open-elements.com/
[13] https://www.oracle.com/
[14] https://www.redhat.com/
[15] https://www.tomitribe.com/
[16] mailto:rme@ix.de
Copyright © 2023 Heise Medien
(Bild: (c) Flickr)
Wer in Crowdfunding-Projekte investiert, muss sich den Risiken bewusst sein. Die Plattformen weisen meist jede Verantwortung von sich.
Crowdfunding hilft beim Anstoßen von Projekten, indem zahlreiche Unterstützer in ein Projekt investieren, an das sie glauben. Der Erfolg ist aber nicht garantiert, und es gilt auf Betrugsmaschen zu achten, zumal die Crowdfunding-Plattformen das Risiko auf die Investoren abwälzen.
Ich möchte mit einer Erfolgsgeschichte beginnen:
Bambu Lab war völlig unbekannt, als das aufstrebende 3D-Drucker-Unternehmen seine X1/X1C-Kampagne über Kickstarter startete. Es sammelte schließlich fast 55 Millionen HK-$ von 5575 Unterstützern. In den folgenden Monaten stellte Bambu Lab die X1/X1C-Produktlinie fertig und schickte alle Perks an die Unterstützer. Dieser neue CoreXY 3D-Drucker erwies sich als revolutionäres, preisgekröntes und äußerst erfolgreiches Produkt, dem bald weitere Produkte wie der P1P und der P1S folgten. Es erübrigt sich zu sagen, dass Bambu Lab eine riesige Erfolgsgeschichte mit einem glücklichen Ende für das Crowdfunding-Unternehmen, den Kampagneninhaber und die Unterstützer war.
Einer der Vorteile von Crowdfunding lässt sich wie folgt zusammenfassen: Crowdfunding-Plattformen bringen innovative Kampagnenmacher und begeisterte Unterstützer zusammen. Sie ermöglichen es Start-ups und etablierten Unternehmen, Finanzmittel für innovative Produkte zu erhalten.
Im Nachhinein betrachtet, funktionieren nicht alle Kampagnen so gut. In einigen Fällen liefern die Werber kein Produkt, stellen nur ein unterdurchschnittliches Produkt her, haben kein Geld mehr oder entpuppen sich als Betrüger. Jahr für Jahr gehen auf diese Weise Millionen von US-Dollar verloren. Es ist nie vorhersehbar, ob ein Projekt erfolgreich sein wird, wie es bei Joint Ventures ebenfalls der Fall ist. Gründe für das Scheitern können die Undurchführbarkeit der Innovation, Budgetüberschreitungen, enorme Projektverzögerungen durch unglückliche Umstände wie Covid-19, zu niedrig angesetzte Kosten oder drastische Preiserhöhungen für notwendige Komponenten sein.
Das Scheitern von Projekten lässt sich zwar nie vermeiden, aber Betrug schon.
Ein chinesischer Kampagnenbetreiber sammelte mit seiner Indiegogo-Kampagne für den kleinsten Mini-PC der Welt über eine Million US-Dollar ein, schaffte aber keine der versprochenen Perks. Nach einiger Zeit gab es sogar keine Kommunikation mehr zwischen den Unterstützern und dem Kampagnenbesitzer. Es schien, als sei der Eigentümer einfach von der Bildfläche verschwunden. Als die Unterstützer Indiegogo um Hilfe baten, fühlte sich das Crowdfunding-Unternehmen nicht zuständig. Sie sperrte einfach alle weiteren Beiträge, versah die Projektwebsite mit dem Hinweis "Diese Kampagne wird derzeit untersucht", lieferte aber weder die Ergebnisse der sogenannten Untersuchung noch eine Rückerstattung an die betrogenen Kunden.
Lektion 1: Crowdfunding-Unternehmen kümmern sich nicht (allzu sehr) um die Unterstützer. Sie verdienen Geld, indem sie eine Plattform für verschiedene Parteien bereitstellen, und behandeln die Geldgeber wie Risikokapitalgeber, die alle Risiken selbst tragen sollen.
Indiegogo und Kickstarter agieren im Grunde wie Wettbüros für Pferderennen, bei denen fast keine Transparenz über die Pferdebesitzer (auch Kampagnenbesitzer genannt) herrscht. Jeder Teilnehmer an solchen Szenarien trägt hohe Risiken, wobei das Wettbüro die einzige Ausnahme ist. Offensichtlich sind die Regeln zwischen den Kunden und der Crowdfunding-Plattform so definiert, dass die Bank (oder das Wettbüro) immer gewinnt.
Lektion 2: Wer sich an einer Crowdfunding-Kampagne beteiligen möchte, sollte mit einem Scheitern des Projekts und dem vollständigen Verlust der Beiträge leben können.
Jeder Unterstützer sollte sich dieser Realität bewusst sein. Er/sie könnte seinen/ihren gesamten Beitrag verlieren oder ein überbezahltes oder sogar nutzloses Produkt erhalten. Sicher, die Mehrheit der Kampagnen ist letztlich erfolgreich. Es gibt jedoch auch eine beträchtliche Anzahl von Kampagnen, die scheitern. Ich mache mir keine Sorgen um das Scheitern von Projekten trotz der enormen Anstrengungen der Kampagnenbetreiber. Das ist ein bekanntes und akzeptables Risiko, das die Geldgeber im Hinterkopf behalten sollten, wenn sie einen Beitrag leisten. Aber ich mache mir Sorgen um betrügerische Kampagnen, bei denen die Betreiber einfach die gesammelten Beiträge nehmen und verschwinden.
Lektion 3: Wer dringend ein bestimmtes Produkt benötigt, sollte sich nicht an einer Crowdfunding-Kampagne beteiligen, sondern es stattdessen bei etablierten Quellen kaufen.
Lektion 4: Derzeit gibt es keine Sicherheitsnetze für Unterstützer. Auch gibt es keine Transparenz oder Rechenschaftspflicht gegenüber den Kampagnenbetreibern. Eine Kampagne gleicht einem Spiel oder einer Wette auf die Zukunft, ohne ausreichende Transparenz in Bezug auf die Eigentümer der Kampagne.
Lektion 5: Man sollte nicht den Videos und Dokumenten trauen, die von Kampagnenbetreibern bereitgestellt werden. Diese Informationen sollte man als eine reine Marketing- und Werbekampagne betrachten und niemals irgendwelchen Versprechungen trauen, vor allem nicht solchen, die unrealistisch oder sehr, sehr schwierig zu erfüllen scheinen. Sätze wie "das weltweit erste", "das weltweit schnellste" oder "das weltweit kleinste" sollten die Unterstützer skeptisch machen.
Was könnte man tun, um solche Situationen zu vermeiden? Oder ist die Crowdfunding-Plattform von Natur aus nicht in der Lage, die Unterstützer zu schützen?
Eigentlich sollte es eine Art Vertrauensverhältnis zwischen allen Akteuren im Spiel geben – ja, es ist ein Spiel beziehungsweise eine Wette! Um das richtige Maß an Vertrauen zu erreichen, muss ein Crowdfunding-Unternehmen folgende Dienstleistungen anbieten:
Eigene Gegenmaßnahmen
Für Backer bzw. Sponsoren einer Crowdfunding-Kampagne existieren aufgrund der beschriebenen Tatsachen zurzeit nur wenige Möglichkeiten, Risiken zu vermeiden. Eine offensichtliche, wenn auch radikale Option wäre, einfach nicht an Crowdfunding-Kampagnen teilzunehmen. Würden alle Nutzer so verfahren, gingen allerdings Anbieter wie Kickstarter in kürzester Zeit pleite. Zudem hätten die ehrlichen, kleinen Kampagnenbetreiber das Nachsehen, die ohne diese Geldquelle ihre Projekte nicht umsetzen könnten.
Daher ist das erste Gebot, sich über den jeweiligen Kampagnenbetreiber detaillierte Informationen zu beschaffen. Natürlich hat diese Informationsakquise ihre Grenzen. Ist der Kampagnenbetreiber eine renommierte, bekannte Firma, die bereits seit Längerem im Geschäft ist und gute Produkte auf den Markt bringt? Falls ja, dürfte das Risiko erheblich geringer sein als bei "dubiosen" oder mehr oder weniger "anonymen" Geschäftspartnern. Inzwischen nutzen auch renommierte Unternehmen zunehmend die Möglichkeit des Crowdfunding.
Eine weitere Möglichkeit besteht darin, sich auf dem Markt nach ähnlichen Produkten umzusehen. Nicht jedes als innovativ angepriesenes Produkt ist es wirklich. Gibt es bereits stark verwandte Alternativen, sollte man besser zu diesen greifen. Es hat insbesondere Vorteile, Produkte von einem Shop zu beziehen, wenn Käufe dort abgesichert sind wie bei Zahlung über Paypal oder Kreditkarte der Fall.
Fast jedes über Kickstarter oder Indiegogo erworbene Produkt ist nach Beendigung der Kampagne über Stores verfügbar, wenn auch zu höheren Preisen. Die Ersparnis durch Finanzierung einer Kampagne gegenüber dem UVP kann schon einiges ausmachen. In manchen Fallen könnte sich ein Abwarten trotzdem vor allem lohnen, wenn die Differenz zwischen Crowdfunding-Preis und Endpreis eher marginal ist. Abwarten hat auch den Vorteil, dass relativ zeitnah nach Produktveröffentlichungen in der Regel auch einige Produktreviews folgen, denen Interessierte die Vorteile und Nachteile des Produkts entnehmen können. Bei Crowdfunding kauft man schließlich die Katze im Sack.
Teilweise finden sich bei der Websuche auch Bewertungen von Kampagnen, bei denen zum Teil Experten ihre Einschätzung verlautbaren. Diese Quellen erweisen sich oft als sehr hilfreich.
Crowdfunding-Sponsoren sollten sich nicht von virtuellen Demo-Videos oder Lobpreisungen des Anbieters mit Superlativen täuschen lassen. Zeigt ein Demovideo nur das grafisch simulierte Produkt, kann das zwar die einzige Option des Herstellers sein, aber auch ein bewusst produziertes Fake. Beispielsweise hat eine Kampagne für 3D-Drucker den Druckkopf in unterschiedlichen Varianten gezeigt, wovon aber bei den angebotenen Perks nie die Rede war. Aber auch bei der Illustration echter Prototypen ist Vorsicht geboten. Dahinter kann sich schließlich Dummy-Funktionalität verbergen. Stutzig sollten Interessierte werden, wenn das Produkt nie im Detail erscheint. Gehen aus der Beschreibung technische Spezifikationen und die Funktionsweise detailliert hervor, so lässt es sich besser einschätzen als bei vagen, lückenhaften, unrealistischen oder schwammigen Aussagen.
Wie bereits erwähnt, sollten Geldgeber ihre eigene Risikobereitschaft unter die Lupe nehmen. Handelt es sich um höhere Geldbeträge, deren Verlust sehr schmerzhaft wäre, sollte man von einer Kampagne Abstand nehmen. Das ist zugegebenermaßen schwer, weil sich Kampagnen unseren instinktiven Hang nach Vergünstigungen und coolen Gadgets zunutze machen. Es geht darum, unsere Entscheidungen bewusst und vernünftig zu treffen. Eine Nacht darüber zu schlafen, ist hier immer die bessere Option, auch wenn dann statt den Super-Early-Bird-Angeboten nur noch Early-Bird-Angebote übrigbleiben. Es empfiehlt sich, nicht gleich zu dem eigentlichen Produkt alles mögliche Zubehör mitzubestellen. Ist das Projekt erfolgreich, lässt sich Zubehör auch im Nachhinein bestellen. Dadurch bleibt der eingesetzte Geld- beziehungsweise Wettbetrag geringer.
Einige mögen argumentieren, dass all diese Maßnahmen die Freiheit der Kampagnenbesitzer einschränken. In dieser Hinsicht haben sie recht. Allerdings besteht derzeit ein Ungleichgewicht zwischen Geldgebern, Kampagneninitiatoren und Crowdfunding-Plattformen, sodass die meisten Risiken bei den Geldgebern liegen. Daher erscheint es mehr als fair, diese Risiken unter allen Beteiligten aufzuteilen. Ich bin der festen Überzeugung, dass sich Crowdfunding zu einer Sackgasse entwickelt, wenn Unternehmen wie Indiegogo weiterhin alle Lasten auf die Unterstützer abwälzen, sich nicht um Betrug kümmern, sich weigern, Sicherheitsnetze zu schaffen, oder die hohe Intransparenz beibehalten. Wenn sie jedoch alle oder zumindest einige der oben genannten Maßnahmen umsetzen, wird sich dies eindeutig als ein Win/Win/Win-Szenario herausstellen.
URL dieses Artikels:
https://www.heise.de/-9297529
Links in diesem Artikel:
[1] mailto:rme@ix.de
Copyright © 2023 Heise Medien
(Bild: raigvi / Shutterstock)
Eine Artikelserie führt in die Grundlagen von Edge AI ein und zeigt, wie sich künstliche neuronale Netze auf eingebetteten System ausführen lassen.
Wenn Softwareentwicklerinnen und Softwareentwickler von Artificial Intelligence (AI) und Machine Learning (ML) sprechen, meinen sie überwiegend künstliche neuronale Netze (KNN), deren Training und Inferenz vorzugsweise auf leistungsfähiger Enterprise-Hardware erfolgt. Dank Werkzeugen wie TensorFlow Lite (Micro) und Edge Impulse ist es heute möglich, zumindest die Inferenz eines neuronalen Netzes auch auf eingebetteten Systemen mit beschränkten Ressourcen durchzuführen. Dies hat den Charme, dass die Verarbeitung von Daten dort stattfinden kann, wo die Daten auch anfallen. Beispiele hierfür umfassen autonomes Fahren und die Automatisierungstechnik.
In einer dreiteiligen Artikelserie bespreche ich ab dem 18. August 2023 zunächst wichtige KI-Grundlagen (Teil 1), danach die Arbeit mit TensorFlow Lite (Teil 2), und zu guter Letzt die Anwendung des MLOps-Online-Werkzeugs Edge Impulse (Teil 3). Im dritten Teil kommt außerdem der KI-gestützte Entwicklerassistent GitHub Copilot zur Sprache, der das Entwickeln eingebetteter Software erlaubt.
Ziel dieser Rundreise ist es, interessierte Leserinnen und Leser mit KI-Technologien und Werkzeugen für eingebettete Systeme vertraut zu machen, sodass sie eigene Experimente durchführen können.
URL dieses Artikels:
https://www.heise.de/-9234664
Links in diesem Artikel:
[1] mailto:map@ix.de
Copyright © 2023 Heise Medien
(Bild: Sundry Photography/Shutterstock.com)
Es gibt nicht nur einen Anbieter für Java-Distributionen und Support. Gartner hat in einem Report die Optionen analysiert, und Oracle schneidet nicht gut ab.
Für die meisten Unternehmen gehören sowohl Java als Programmiersprache als auch die JVM als Laufzeitumgebung zur kritischen Infrastruktur, auf der ein großer Teil unserer digitalen Welt aufgebaut ist. Da für die kritische Infrastruktur im Enterprise-Umfeld immer geraten wird, über passende Support-Möglichkeiten zu verfügen, besitzen viele Firmen einen kommerziellen Support-Vertrag für Java. Diese Verträge wurden historisch oft mit Oracle geschlossen, da das Unternehmen lange Zeit der prominenteste Anbieter für Java-Distributionen war. Da sich aber in den letzten Jahren viel in diesem Bereich getan hat, stellt sich die Frage, ob Oracle hier noch immer der beste Partner für den kommerziellen Support von Java ist.
In vielen Bereichen der heutigen Welt ist Diversität ein wichtiges Thema, da hierdurch neue Ansichten, Lösungen und Möglichkeiten entstehen. Auch im Bereich der Java-Laufzeitumgebungen hat sich hier in den letzten Jahren einiges getan. Die Zeit, in der Entwicklerinnen und Entwickler Java prinzipiell von Oracle heruntergeladen haben, da es der Platzhirsch bei Java-Distributionen war, ist lange vorbei. Durch die fortschreitende Beliebtheit von Open Source arbeiten mehr und mehr Firmen am OpenJDK [1] mit, der quelloffenen Implementierung der Java Standard Edition. Da auch die Distributionen von Oracle aus diesen Quellen gebaut werden, kann man somit auch problemlos zu Alternativen greifen. Dazu kommt, dass durch das Java Test Compatibility Kit (TCK) viele der Laufzeitumgebungen auf ihre Kompatibilität zum Standard hin überprüft werden.
Eclipse hat einen Marketplace eröffnet [2], in dem alle TCK und AQAvit verifizierten beziehungsweise lizenzierten Java-Laufzeitumgebungen zum Download angeboten werden. Neben verschiedenen Builds von Firmen wie Microsoft oder Azul tut sich hier vor allem Eclipse Temurin [3] hervor, das als einzige Java Distribution eine herstellerunabhängige Variante ist. Verantwortlich ist die Eclipse-Adoptium-Arbeitsgruppe, der Firmen wie Microsoft [4], Red Hat [5], Google [6] und Open Elements [7] angehören. Mit über 200 Millionen Downloads [8] gibt es auch keine andere Java-Distribution, die aufgrund ihrer Zahlen auch nur ansatzweise an die Verbreitung von Eclipse Temurin herankommt. Dies unterstreicht ein aktueller Gartner-Bericht [9], laut dem 2026 über 80 Prozent aller Java Anwendungen voraussichtlich nicht auf einer Oracle-Distribution laufen werden. Eclipse Temurin macht hier am Ende sicherlich das größte Stück des Kuchens aus.
Neben der Analyse verschiedener Distributionen hat Gartner die verschiedenen Möglichkeiten der kommerziellen Support-Optionen für Java untersucht. Hierbei hat vor allem der Oracle-Support nicht wirklich gut abgeschnitten:
Oracle hat erneut die Lizenzregeln für seine Java-Distributionen geändert. Am 23. Januar 2023 führte das Unternehmen eine neue Lizenzmetrik ein, die SE Universal Subscription [10]. Das kontroverse Preismodell zieht als Basis die Gesamtzahl der Kundenmitarbeiter heran und nicht die Anzahl der Mitarbeiter, die die Software nutzen. Möglicherweise versucht Oracle so, dass Kunden keine anderen Support-Modelle für Java in Erwägung ziehen. Für Kunden führt das in vielen Fällen dazu, dass die Support-Kosten für Java durch die Decke gehen. Laut Gartner entstehen hier für die meisten Organisationen zwei- bis fünfmal so hohe Kosten. Dazu kommt natürlich, dass man durch einen kommerziellen Support bei Oracle nur Support für die Oracle-Distribution erhält und nicht etwa für das immer beliebter werdende Eclipse Temurin. Vielleicht ist daher genau jetzt der richtige Zeitpunkt, sich einmal nach alternativen Angeboten für Java Support umzuschauen.
Natürlich haben Organisationen wie Azul oder auch die Arbeitsgruppe von Eclipse Adoptium auf diese Ereignisse reagiert. Mike Milinkovich, der Executive Director der Eclipse Foundation [11], kommentierte das Ganze wie folgt auf X / Twitter:
(Bild: Twitter / X)
Aber neben diesem Kommentar ließ Adoptium auch Taten sprechen und hat eine Seite für kommerziellen Support für Eclipse Temurin [12] eingerichtet. Auf dieser wird mit Red Hat, IBM und Open Elements gleich durch drei Experten der Branche Support für Eclipse Temurin angeboten. Hervorzuheben ist hierbei für den deutschsprachigen Raum Open Elements, da diese mit ihrem "Support & Care"-Paket [13] nicht nur aktiv Temurin als Open-Source-Projekt fördern, sondern auch Support in deutscher Sprache anbieten.
Wie man sehen kann, ist auch das Angebot an Support-Optionen für die Java Laufzeitumgebung in den letzten Jahren deutlich diverser geworden. Neben dem Angebot von Oracle gibt es nun verschiedene Möglichkeiten, die oft deutlich besser auf die eigenen Anforderungen zugeschnitten sind. Natürlich bedeutet das auch, dass jede Firma über den Tellerrand schauen und Alternativen zum bereits vor Jahren abgeschlossenen Oracle-Support begutachten und bewerten muss. Für Firmen, die bisher über keinen Support-Vertrag verfügen, eignen sich einige der Angebote vielleicht deutlich eher, als direkt mit dem Riesen Oracle einen Vertrag zu schließen. Aber auch das Angebot von Oracle hat seine Berechtigung und wird für einige Firmen das passende sein. Daher will ich hier nicht ein Angebot als Gewinner darstellen, sondern die Vielfalt der Möglichkeiten dank Firmen wie IBM, Red Hat, Azul, Open Elements und natürlich Oracle als Gewinn für die gesamte Java Community aufzeigen.
URL dieses Artikels:
https://www.heise.de/-9232113
Links in diesem Artikel:
[1] https://openjdk.org/
[2] https://adoptium.net/marketplace/
[3] https://adoptium.net/de/
[4] https://www.microsoft.com/openjdk
[5] https://www.redhat.com
[6] https://cloud.google.com/java
[7] https://open-elements.com/
[8] https://dash.adoptium.net/
[9] https://www.gartner.com/en/documents/4540799
[10] https://www.oracle.com/us/corporate/pricing/price-lists/java-se-subscription-pricelist-5028356.pdf
[11] https://www.eclipse.org
[12] https://adoptium.net/temurin/commercial-support/
[13] https://open-elements.com/temurin-support/
[14] mailto:rme@ix.de
Copyright © 2023 Heise Medien
Die Verbindung von eingebetteten Systemen und Servern kann über REST-Kommunikation erfolgen
(Bild: pixabay.com)
Wie kommuniziert ein Arduino-Board serverseitig mit einer REST-API, um Sensor-Messungen zu speichern? Nach dem Server in Teil 1 steht die Client-Seite an.
Arduino-Boards oder andere Microcontroller-Boards beziehungsweise eingebettete Systeme oder Einplatinencomputer können mit einer REST-Anwendung kommunizieren, wenn sie einen WiFi- oder Ethernet-Anschluss integrieren. Da ein typischer Anwendungsfall wie der hier vorgestellte überwiegend Messungen mit Sensoren durchführt, erweist eine Echtzeituhr (RTC) als Vorteil. Dadurch lassen sich zusätzlich zu den Messdaten Zeitstempel (Datum und Zeit) hinzufügen, die später eine historische Analyse ermöglichen, etwa den Verlauf der Temperatur über einen bestimmten Zeitraum.
Auch wenn hier vom Arduino Giga die Rede ist, beschränkt sich die Anwendung keineswegs nur auf dieses Board, sondern lässt sich relativ leicht für andere Arduino-, STM- oder ESP-Boards anpassen, für die funktional ähnliche Bibliotheken wie WiFi, WiFiUdp und HTTPClient in der Arduino IDE oder in Visual Studio Code mit PlatformIO-Plug-in bereitstehen.
Notwendig ist ein Sensor von Bosch Sensortec aus der BME-Familie (BME68x, BME58x, BME38x, BME28x), den Maker über I2C oder SPI anschließen. Für das Beispiel kam ein BME688 zum Einsatz, den es als Breakout-Board von verschiedenen Herstellern wie Pomoroni oder Adafruit gibt. Wer möchte, kann auch ein Bosch Development Kit kaufen. Dieses Board besteht aus einem BME688-Adapter, der huckepack auf einem Adafruit Huzzah32 Feather Board (ESP32) sitzt. Das schlägt mit rund 100 Euro zu Buche, hat aber den Charme, sich mittels der kostenlosen BME-AI-Studio-Software trainieren zu lassen. Der Sensor lernt dabei, über ein neuronales Netz verschiedene Gasmoleküle in der Luft zu erkennen, wie Kaffee, CO₂ oder Chlor. Er ermittelt für die Moleküle in der Luft Widerstände in Ohm und kann über die elektrische Charakteristik Gase klassifizieren. Diese spezifischen ML-Trainings sind mit Breakout-Boards freilich nicht möglich – der Anwender erfährt dort nur den Widerstand des Gases ohne Zuordnung zu einem konkreten Molekül. Dafür kosten die einfachen Boards auch nur zwischen 20 und 30 Euro.
Wie bereits im ersten Teil [1] erwähnt, findet sich der Quellcode für die Beispiele in dem GitHub Repository [2].
Ein Hinweis vorab: Als mögliche Alternative für die hier vorgestellte Integration über eine RESTful-Architektur käme beispielsweise der Einsatz von MQTT infrage, das eine ereignis- beziehungsweise nachrichtenorientierte Kommunikation zwischen Client und Server ermöglicht. In vielen Fällen, vielleicht auch für den hier vorliegenden, ist MQTT eine sehr gute Option. Allerdings fokussieren sich die beiden Artikelteile speziell darauf, Kommunikation und Backend-Integration über REST-APIs zu veranschaulichen. Ein vom Backend betriebener Microservice stellt schließlich typischerweise eine REST-API bereit.
In der konkreten Schaltung sind die folgenden Verbindungen notwendig:
Arduino ........ BME688
3.3V ............ Vin
GND ............. GND
SCL ............. SCK
SDA ............. SDI
Folgendes Fritzing-Diagramm veranschaulicht dies:
Beim Entwickeln der Arduino-Software sind folgende Verantwortlichkeiten zu beachten:
Initiales Setup:
Kontinuierliche Loop:
Nebenbei soll der Sketch Informationen über den seriellen Monitor zu Test- und Beobachtungszwecken ausgeben.
Der Arduino-Sketch erwartet im selben Verzeichnis eine Datei arduino_secrets.h, die Definitionen für das gewünschte WLAN enthalten, konkret die SSID und den WLAN-Key. Für die Nutzung des Arduino-WiFis bedarf es einer entsprechenden WiFi-Bibliothek, deren Header wir inkludieren, zum Beispiel: #include <WiFi.h>. Zusätzlich benötigen wir die Bibliothek ArduinoHttpClient, die wir über den Library Manager der Arduino IDE oder VS Code & PlatformIO installieren.
Den Verbindungsaufbau zum WLAN übernimmt nachfolgender Code:
void setup() {
// Seriellen Stream starten
Serial.begin(9600);
// Verbindungsaufbau WiFi
while (status != WL_CONNECTED) {
Serial.print("Versuche Verbindungsaufbau zum WLAN-Netz: ");
Serial.println(ssid); // print the network name (SSID);
// Verbinden mit WPA/WPA2 Netzwerk:
status = WiFi.begin(ssid, pass);
}
// Name des WLANs nach erfolgtem Verbindungsaufbau:
Serial.print("SSID: ");
Serial.println(WiFi.SSID());
Anschließend soll der Zugriff auf einen NTP-Server zum Einstellen der Echtzeituhr verhelfen.
Beim Giga R1 Board sind dazu folgende Header-Dateien notwendig:
#include <WiFiUdp.h> // zum Versenden von NTP-Paketen notwendig
#include <mbed_mktime.h>
Das schaut auf anderen Boards natürlich anders aus und wäre diesbezüglich zu ändern.
Der eigentliche Code für das Setzen der Echtzeituhr befindet sich in der Methode setNtpTime(), die ihrerseits eine UDP-Verbindung mit einem lokalen Port öffnet, ein Anfragepaket an den gewünschten Zeitserver verschickt (sendNTPacket()), um danach die Antwort über parseNtpPacket() zu empfangen, zu verarbeiten und die Echtzeituhr mit der aktuellen Zeit einzustellen. Hat dies erfolgreich geklappt, lässt sich fortan über die Methode getLocalTime() stets die aktuelle Zeit aus der Echtzeituhr auslesen. Zurückgeliefert wird jeweils ein Datetime-String, der sich aus Datum und Zeit zusammensetzt: „2023-06-05 12:31:45“.
// NTP-Anfrage senden, Antwort erhalten und parsen
void setNtpTime()
{
status = WL_IDLE_STATUS;
Udp.begin(localPort);
sendNTPpacket(timeServer);
delay(1000);
parseNtpPacket();
}
// Rufe einen Zeitserver auf
unsigned long sendNTPpacket(const char * address)
{
memset(packetBuffer, 0, NTP_PACKET_SIZE);
packetBuffer[0] = 0b11100011; // LI, Version, Modus
packetBuffer[1] = 0; // Stratum, Art der Uhr
packetBuffer[2] = 6; // Abfrageintervall
packetBuffer[3] = 0xEC; // Präzision der Uhr
// 8 Bytes von Nullen für Root Delay & Root Dispersion
packetBuffer[12] = 49;
packetBuffer[13] = 0x4E;
packetBuffer[14] = 49;
packetBuffer[15] = 52;
Udp.beginPacket(address, 123); // NTP Aufrufe erfolgen über Port 123
Udp.write(packetBuffer, NTP_PACKET_SIZE);
Udp.endPacket();
}
// Hier wird das NTP-Antwortobjekt empfangen und geparst:
unsigned long parseNtpPacket()
{
if (!Udp.parsePacket())
return 0;
Udp.read(packetBuffer, NTP_PACKET_SIZE); // Paket vom NTP-Server empfangen
const unsigned long highWord = word(packetBuffer[40], packetBuffer[41]);
const unsigned long lowWord = word(packetBuffer[42], packetBuffer[43]);
const unsigned long secsSince1900 = highWord << 16 | lowWord;
constexpr unsigned long seventyYears = 2208988800UL;
const unsigned long epoch = secsSince1900 - seventyYears;
set_time(epoch);
// Folgende ausführliche Beschreibung lässt sich ausgeben,
// sobald man DETAILS definiert
#if defined(DETAILS)
Serial.print("Sekunden seit Jan 1 1900 = ");
Serial.println(secsSince1900);
// NTP time in "echte" Zeit umwandeln:
Serial.print("Unix Zeit = ");
// Ausgabe der Unix time:
Serial.println(epoch);
// Stunde, Minute, Sekunde ausgeben:
Serial.print("Die UTC Zeit ist "); // UTC entspricht Greenwich Meridian (GMT)
Serial.print((epoch % 86400L) / 3600); // Stunde ausgeben (86400 sind die Sekunden pro Tag)
Serial.print(':');
if (((epoch % 3600) / 60) < 10) {
// In den ersten 10 Minuten einer Stunde brauchen wir eine führende Null
Serial.print('0');
}
Serial.print((epoch % 3600) / 60); // Minute ausgeben (3600 = Sekunden pro Stunde)
Serial.print(':');
if ((epoch % 60) < 10) {
// In den ersten 10 Minuten einer Stunde brauchen wir eine führende Null
Serial.print('0');
}
Serial.println(epoch % 60); // Sekunde ausgeben
#endif
return epoch;
}
// Lokale Zeit mittels RTC (Real Time Clock) ermitteln:
String getLocaltime()
{
char buffer[32];
tm t;
_rtc_localtime(time(NULL), &t, RTC_FULL_LEAP_YEAR_SUPPORT);
strftime(buffer, 32, "%Y-%m-%d %k:%M:%S", &t);
return String(buffer);
}
Für Interessierte, die mehr über das NTP (Network Time Protocol) erfahren möchten, eine Abbildung mit anschließenden Erläuterungen, die den Aufbau eines NTP-Pakets veranschaulichen:
Die Header-Einträge eines NTP-Pakets gestalten sich oben wie folgt:
LI
Leap Indicator (2 bits)
Dieses Attribut zeigt an, ob die letzte Minute des aktuellen Tages eine Zusatzsekunde benötigt.
0: Keine Sekundenanpassung nötig
1: Letzte Minute soll 61 sec haben
2: Letzte Minute soll 59 sec haben
3: Uhr wird nicht synchronisiert
VN
NTP-Versionsnummer (3 Bits) (etwa Version 4).
Mode
NTP-Paketmodus (3 Bits)
0: Reserviert
1: Symmetrisch aktiv
2: Symmetrisch passiv
3: Client
4: Server
5: Broadcast
6: NTP-Kontrollnachricht
7: Für private Nutzung reserviert
Stratum
Stratum-Ebene der Zeitquelle (8 bits)
0: Unspezifiziert oder ungültig
1: Primärer Server
2–15: Sekundärer Server
16: Unsynchronisiert
17–255: Reserviert
Poll
Poll Interval (8-Bit Ganzzahl mit Vorzeichen), die in Sekunden definiert, was das maximale Zeitintervall zwischen aufeinander folgenden NTP-Nachrichten sein soll.
Precision
Präzision der Uhr (8-Bit Ganzzahl mit Vorzeichen)
Root Delay
Die Round-Trip-Verzögerung vom Server zur primären Zeitquelle. Es handelt sich um eine 32-Bit Gleitpunktzahl, die Sekunden festlegt. Der Dezimalpunkt befindet sich zwischen Bits 15 und 16. Sie ist nur für Server-Nachrichten relevant.
Root Dispersion
Der maximale Fehler aufgrund der Toleranz bezüglich der Uhr-Frequenz. Es handelt sich um eine 32-Bit Gleitpunktzahl, die Sekunden festlegt. Der Dezimalpunkt befindet sich zwischen Bits 15 und 16. Sie ist nur für Servernachrichten relevant
Reference Identifier
Bei Stratum-1-Servern beschreibt dieser Wert (ein ASCII-Wert mit 4 Bytes) die externen Referenzquellen. Für sekundäre Server beschreibt der Wert (4 Bytes) die IPv4-Adresse der Synchronisationsquelle, oder die ersten 32 Bit des MDA-Hashes (MDA = Message Digest Algorithm 5) der IPv6-Adresse des Synchronisationsservers.
Um den BME688-Sensor über I2C anzusteuern, müssen wir Bibliotheken von Adafruit über den Bibliotheksmanager laden - der Code lässt sich einfach umstellen, um stattdessen SPI zu verwenden. Importieren muss man die Bibliotheken Adafruit BME680 und Adafruit Unified Sensor. Es kann natürlich auch die Bibliothek eines anderen Sensors aus der BME-Familie von Bosch sein, etwa die eines BME280.
Die Initialisierung des Sensors ist in setup() integriert. Hier die Vereinbarung des BME-Proxies:
Adafruit_BME680 bme; // Vereinbarung der Variablen bme
Und hier die eigentliche Initialisierung:
// Verbindung mit Sensor BME680 etablieren
if (!bme.begin()) {
Serial.println(F("Konnte keinen Sensor finden. Bitte Schaltung überprüfen!"));
while (1);
}
// Oversampling-Werte setzen und IIR-Filter initialisieren
bme.setTemperatureOversampling(BME680_OS_8X);
bme.setHumidityOversampling(BME680_OS_2X);
bme.setPressureOversampling(BME680_OS_4X);
bme.setIIRFilterSize(BME680_FILTER_SIZE_3);
bme.setGasHeater(320, 150); // 320*C für 150 ms
}
Ab geht die POST
Die Methode createJSONString() konstruiert aus Messdaten und Zeitstempel ein JSON-Objekt. Ich habe hier bewusst ein manuelles Erstellen gewählt, statt ArduinoJson zu verwenden, weil der Aufwand sehr übersichtlich bleibt:
// Hier wird ein String generiert, der ein JSON-Objekt enthält
String createJSONString(double temp, double humi, double pres, double resi, String date, String time) {
String result = "{";
const String up = "\"";
const String delim = ",";
const String colon = ":";
result += up + "temperature" + up + colon + String(temp,2) + delim;
result += up + "humidity" + up + colon + String(humi,2) + delim;
result += up + "pressure" + up + colon + String(pres,2) + delim;
result += up + "resistance" + up + colon + String(resi,2) + delim;
result += up + "date" + up + colon + up + date + up + delim;
result += up + "time" + up + colon + up + time + up;
result += "}";
return result;
}
Wer einen Sensor besitzt, der einige dieser Messdaten nicht enthält, stutzt das JSON-Objekt entsprechend. In der Server-Datenbank wird für jedes nicht übergebene Attribut einfach ein null eingetragen.
Für das Versenden der Messdaten über HTTP-POST ist callAPIPost() verantwortlich, das anschließend auch die Antwort des Servers ausgibt, der im Erfolgsfall mit Status-Code 200 antwortet:
// Hier erfolgt die Vorbereitung und die eigentliche Durchführung des POST-Aufrufes / REST-PUSH
void callAPIPost(double temperature, double humidity, double pressure, double resistance, const String& date, const String& time){
Serial.println("Durchführung eines neuen REST API POST Aufrufs:");
String contentType = "application/json"; // Wir übergeben ein JSON-Objekt
// JSON Nutzlast (Body) kreieren
String postData = createJSONString(temperature, humidity, pressure, resistance, date, time);
Serial.println(postData); // ... und ausgeben
// Per WiFi-Verbindung POST aufrufen und dabei "application/json" und das JSON-Objekt übergeben
client.post("/measurements/api", contentType, postData);
// Status und Body aus Antwort extrahieren
int statusCode = client.responseStatusCode();
String response = client.responseBody();
// Ausgabe der Ergebnisse:
Serial.print("Status code: ");
Serial.println(statusCode);
Serial.print("Antwort: ");
Serial.println(response);
}
Aufrufe der vorgenannten Methoden finden in der loop()-Methode statt, außer natürlich die Initialisierungen beim Programmstart.
Damit ist der Arduino-Sketch fertig, der den REST-Server aus Teil 1 mit neuen Messungen versorgt. Natürlich gibt es wie immer Erweiterungsmöglichkeiten:
Hier werden der Anschluss für 3.3V, GND, SDA, SCL vom Arduino auf ein Breadboard geführt, von wo sie IIC-Display und IIC-Sensor abgreifen können.
Und das ist immer noch nicht das Ende der Fahnenstange. Die Möglichkeiten sind fast unendlich. Client- und Server-Anwendungen des Beispiels sollten das Vorgehen dabei gut veranschaulichen und zu eigenen Experimenten anreizen. Die Implementierungen für Client und Server lassen sich entsprechend anpassen.
Ich hoffe jedenfalls, der hier vorgestellte Showcase bietet viel Nutzen und macht Spaß.
URL dieses Artikels:
https://www.heise.de/-9179967
Links in diesem Artikel:
[1] https://www.heise.de/blog/Gib-mir-den-REST-Teil-1-Der-REST-Server-9179764.html
[2] https://github.com/ms1963/RESTCommunication
[3] https://www.heise.de/blog/Fernsteuerung-per-Computer-GPIO-Breakout-Boards-als-Schweizer-Taschenmesser-7091889.html
[4] mailto:rme@ix.de
Copyright © 2023 Heise Medien
(Bild: Shutterstock)
Das ehemals kostenpflichtige GraalVM Enterprise ist jetzt als Oracle GraalVM frei verfügbar. Die Versionsnummerierung wurde zudem dem OpenJDK angepasst.
GraalVM ist eine in Java implementierte JVM (Java Virtual Machine) und ein JDK (Java Developement Kit) basierend auf der Hotspot-VM und dem OpenJDK. Es unterstützt mit GraalVM Native Image unter anderem die AOT-Kompilierung (Ahead of Time) von Java Anwendungen für schnellere Startzeiten und geringeren Speicherverbrauch zur Laufzeit. Initial bei Sun gestartet wird es nun bei den Oracle Labs parallel zu dem klassischen Java OpenJDK entwickelt und war bisher sowohl in einer Open Source lizenzierten Community Edition als auch einer kommerziellen Enterprise Edition verfügbar.
Die Besonderheiten zum normalen Java JDK sind:
Ende 2022 hat Oracle die GraalVM Community Edition an das OpenJDK-Projekt übergeben [1] und damit die Basis des Projekts als Open Source bereitgestellt. Dabei wurde angekündigt, zukünftig die Versionen und Nummerierungen an die Release-Zyklen des OpenJDK anzugleichen. Die erste produktionsreife Version war GraalVM 19.0 im Mai 2019 [2]. Mit GraalVM 22.3.2 kam im April 2023 das bisher letzte Release nach dem alten Nummerierungsschema.
Nun sind Oracle GraalVM für das JDK 17 und Oracle GraalVM für das JDK 20 erschienen [3]. Diese früher als kommerzielle Oracle GraalVM Enterprise bekannten Versionen stehen ab sofort kostenfrei unter der GraalVM Free Terms and Conditions (GFTC) Lizenz zur Verfügung. Diese Lizenz erlaubt die kostenlose Nutzung für alle Benutzer, auch für den Produktionseinsatz. Die Weiterverbreitung ist erlaubt, wenn sie nicht gegen eine Gebühr erfolgt. Entwickler und Organisationen können Oracle GraalVM jetzt einfach herunterladen, verwenden, weitergeben und weiterverteilen, ohne dass sie eine Lizenzvereinbarung durchklicken müssen. Oracle wird weiterhin die GPL-lizenzierten Versionen der GraalVM Community Edition zu den gleichen Bedingungen wie die Oracle-Builds des OpenJDK anbieten.
Etwas unklar ist im Moment die Frage, ob man die eigene Software, die als GraalVM Native Image gebaut wurde, nach GFTC-Lizenz verkaufen darf. Unser Leser Michael Paus hat uns auf eine Diskussion im GraalVM Slack [4] aufmerksam gemacht (um diesen Beitrag lesen zu können, muss man sich zuvor als Benutzer registrieren [5]). Nach dem aktuellen Stand der Diskussion darf man als GraalVM Native Image gebaute Software in Produktion nur kostenfrei für die interne Nutzung oder als Open Source betreiben. Verlangt man eine Gebühr für die eigene Software, muss man auf eine kommerzielle Lizenz von GraalVM ausweichen. Die Kosten dafür richten sich entweder nach den Prozessorkernen, auf denen das Native Image laufen wird oder es wird ein nicht geringer Pauschalbetrag fällig. Auskünfte dazu holt man sich am besten direkt beim Sales & Tech Team bei Oracle ein.
Kurzer Blick auf das klassische Java OpenJDK: Im September 2021 hatte Oracle angekündigt, dass das Oracle JDK (Oracles Variante des OpenJDK) wieder kostenfrei zur Verfügung steht (unter der Oracle No-Fee Terms and Conditions Lizenz - NFTC). Zuvor hatte Oracle 2018 mit dem JDK 8 und 11 eine kommerzielle Lizenz für die produktive Nutzung des Oracle JDK eingeführt.
Alternativ gab es zwar noch das binär-kompatible Oracle OpenJDK kostenfrei, welches aber immer nur maximal ein halbes Jahr mit Updates und Patches versorgt wurde und somit die Entwickler zu halbjährlichen JDK Versionsupdates gezwungen hätte. Seit dieser Zeit sind viele andere auf dem OpenJDK basierende Distributionen von Amazon, IBM, SAP, Microsoft usw. herausgekommen beziehungsweise haben an Bedeutung gewonnen.
Die bekannteste Variante ist das AdoptOpenJDK (mittlerweile Temurin vom Eclipse Projekt Adoptium [6]), welches durch Oracles Lizenz-Wirrwar zum meistverwendeten JDK wurde. Oracle hat das erkannt und durch die erneute kostenfreie Weitergabe des Oracle JDK versucht, Marktanteile zurückzugewinnen. Diesen Ansatz weiten sie nun auch auf Oracle GraalVM mit der GraalVM Free Terms and Conditions (GFTC)-Lizenz aus.
Analog dem OpenJDK wird es nun ebenfalls Long Term Support Releases (aktuell GraalVM für JDK 17) geben, die bis zu einem vollen Jahr nach der Veröffentlichung des nachfolgenden LTS-Release (GraalVM für JDK 21) kostenlos mit Updates versorgt werden. Releases, die nicht als LTS-Releases gekennzeichnet sind (wie GraalVM für JDK 20), werden so lange mit Aktualisierungen versorgt, bis sie durch das nächste Release abgelöst werden. Entwickler können somit jetzt auch bei GraalVM alle sechs Monate auf die neueste JDK-Version aktualisieren und erhalten so sofort Zugang zu den neuesten Java-Funktionen. Alternativ haben Sie die Möglichkeit, zwischen den LTS-Versionen zu wechseln.
Die neuen Oracle GraalVM-Releases lassen sich dank neuer stabiler Download-URLs jetzt leichter in CI/CD-Build-Pipelines einbinden. Die Download-Artefakte enthalten zudem das Native-Image-Dienstprogramm. Somit ist alles in einem einzigen Paket unter derselben Lizenz verfügbar, was für die Entwicklung mit der GraalVM benötigt wird. Für containerisierte Anwendungen oder Container-basierte Builds werden zudem bald neue GraalVM Container-Images auf der Oracle Container Registry verfügbar sein.
Das GraalVM Projekt hat in den vergangenen Jahren eine Menge Aufmerksamkeit erhalten. Das Ziel ist, Javas Rückstand auf moderne Programmiersprachen wie Go in Bezug auf schnelle Startzeiten und effizientes Speicherverhalten zur Laufzeit zu verkleinern. Java ist zwar weiterhin als stabile Laufzeitumgebung für serverseitige Anwendungen vertreten, verliert aber insbesondere beim Umzug in die Cloud Marktanteile an Go & Co. Mit der GraalVM hat man nun auf der Java Plattform die Wahl und kann mit den bestehenden Programmierkenntnissen beide Welten unterstützen. Die Angleichung der Release-Zyklen und der Versionierung an das OpenJDK lassen uns die neuesten Features sowohl im "normalen" Java als auch bei der GraalVM nutzen.
Die Änderungen an den neuen, im Juni 2023 erschienenen GraalVM-Versionen hat Alina Yurenko in ihrem Blog-Post [7] zusammengefasst.
Die neuen Oracle GraalVM Versionen können von der Java-Download-Seite [8] bezogen werden. Weitere Informationen [9] finden sich in den Installationsanleitungen, der Dokumentation und den Versionshinweisen. In der Oracle Cloud Infrastruktur kann Oracle GraalVM übrigens kostenfrei genutzt werden.
Nach Hinweis eines Lesers wurde der Beitrag um einen Absatz ergänzt, der sich den Unklarheiten bezüglich der Bedingungen zum Verkauf eigener Software widmet, die mit GraalVM Native Image gebaut wurden.
URL dieses Artikels:
https://www.heise.de/-9199872
Links in diesem Artikel:
[1] https://www.heise.de/news/Virtuelle-Maschine-Oracle-uebergibt-GraalVM-Community-Edition-an-OpenJDK-7320608.html
[2] https://www.heise.de/news/GraalVM-19-das-erste-offiziell-produktionsreife-Release-4420777.html
[3] https://blogs.oracle.com/cloud-infrastructure/post/graalvm-free-license
[4] https://t.co/Ga1lgeLr91
[5] https://t.co/JFAWTJ9niQ
[6] https://www.heise.de/news/AdoptOpenJDK-landet-bei-der-Eclipse-Foundation-4789835.html
[7] https://medium.com/graalvm/a-new-graalvm-release-and-new-free-license-4aab483692f5
[8] https://www.oracle.com/java/technologies/downloads/
[9] https://docs.oracle.com/en/graalvm/jdk/20/
[10] mailto:rme@ix.de
Copyright © 2023 Heise Medien
(Bild: Black Jack/Shutterstock.com)
Logging ein wichtiger Teil der Fehleranalyse. Allerdings ist das Zusammenführen unterschiedlicher Logging Libs in Java-Anwendungen immer eine Herausforderung.
Nachdem ich im ersten Post zum Thema Java Logging [1] auf Best Practices und Pitfalls eingegangen bin, möchte ich nun einmal auf die Nutzung von Logging in einem großen Projekt eingehen. In diesem Bereich kommt es oft zu Problemen zwischen verschiedenen Logging Frameworks und eine Zusammenführung des gesamten Anwendung-Logging kann sich mitunter schwer gestalten.
Um die Problematik besser zu verstehen, fange ich mit einem ganz einfachen Beispiel, quasi dem Hello World im Logging, an. Der folgende Code zeigt eine minimale Java-Anwendung, welche einfach eine Nachricht loggt:
public class HelloLogging {
private static final Logger LOG = Logger.getLogger("HelloLogging");
public static void main(final String[] args) {
LOG.info("Hello World");
}
}
Bereits in dieser trivialen Anwendung kann das Logging durch die Features des Logging Frameworks, im Beispiel etwa java.util.Logging (JUL), konfiguriert und in einer Datei oder der Konsole (Shell) ausgegeben werden. Das folgende Diagramm zeigt den Aufbau und die Konfiguration des Logging in einem schematischen Aufbau.
Während der gegebene Aufbau für ein kleines Projekt gut funktioniert, wird es manchmal schon problematisch, sobald die ersten Abhängigkeiten hinzukommen. Stellen wir uns einmal vor, dass wir zwei Abhängigkeiten für unsere Anwendung benötigen: eine Library, um den Zugriff zu einer Datenbank zu gewährleisten und eine weitere Abhängigkeit zu einer Security Library, um unsere Anwendung sicher vor Angriffen zu machen. Da die Entwickler dieser Bibliotheken auch Information über dessen Zustand, Nutzung und Laufzeitfehler ausgeben wollen, nutzen diese ebenfalls Logging. Allerdings wird bei diesen Bibliotheken nicht java.util.Logging, sondern es werden andere Logging Libraries genutzt. Wie man im folgenden Diagramm sehen kann, nehmen wir an, dass Log4J2 [2] und Logback [3] im Einsatz sind.
Nun haben wir das Problem, dass das Logging unserer Anwendung über drei verschiedene Logging-Frameworks geleitet wird. Zwar bieten Log4J und Logback auch genug Möglichkeiten der Konfiguration, aber da die Logging Frameworks sich nicht gegenseitig synchronisieren, wäre es eine ganz dumme Idee, alle Frameworks in die gleiche Datei schreiben zu lassen. Hier kann es passieren, dass mehrere der Frameworks in die gleiche Zeile schreiben und es somit zu einem unleserlichen Haufen zufällig aneinandergereihter Textbausteine oder sogar zu Deadlocks kommen kann. Eine andere Idee ist es, dass jedes Framework in eine eigene Datei loggt, wie es im folgenden Diagramm angedeutet ist.
Dieser Aufbau führt dazu, dass die Loggings völlig unabhängig voneinander agieren und sich nicht in die Quere kommen können. Hierdurch hat man zwar ein sauberes Logging, das aber auf mehrere Dateien verteilt ist, die man manuell oder mithilfe von Tools synchronisieren muss. Dazu kommt, dass man immer alle vorhandenen Loggingsysteme konfigurieren muss, wenn man beispielsweise einmal ein höheres Logging-Level zur Analyse der Anwendung aktivieren möchte. Erschwerend kommt hinzu, dass man in einem echten Projekt mehr als nur zwei Abhängigkeiten hat uns es somit zu noch deutlich mehr Logging-Frameworks kommen kann, die im Einsatz sind.
Logging Facades schaffen hier Abhilfe. Durch eine Facade kann man den Code von einer konkreten Implementierung trennen. Die Simple Logging Facade for Java [4] (SLF4J) hat sich hier ganz klar als Standard durchgesetzt. SLF4J bietet eine Logging-API, die als einzelne Abhängigkeit ohne transitiven Abhängigkeiten daher kommt und problemlos in so ziemlich jedes System eingebunden werden kann. Die API kann in diesem Fall genutzt werden, um konkrete Log-Aufrufe im Code zu genieren. Der folgende Code zeigt ein „Hello World“ Logging Beispiel:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class HelloLogging {
private static final Logger logger =
LoggerFactory.getLogger(HelloLogging.class);
public static void main(String[] args) {
logger.info("Hello World!");
}
}
Betrachtet man rein diesen Code kann man sich fragen, welche Vorteile SLF4J gegenüber dem klassischen Java-Logging bringen soll. Einer der wichtigsten Punkte ist, dass es sich bei org.slf4j.Logger um ein Interface handelt. Das Modul slf4j-api, das die SLF4J-Api und somit das genannte Interface enthält, liefert keine Implementierung des Interfaces. Das gesamte Modul definiert lediglich die öffentliche API api von SLF4J, die Logging Facade.
Um sie zu nutzen, müssen wir eine Implementierung bereitstellen. Hierfür muss ein sogenanntes Binding als Abhängigkeit hinzugefügt werden, das eine Implementierung der Logging Facade bereitstellt. In der Regel leitet ein solches Binding die Logging-Events an ein konkretes Logging-Framework weiter. Möchte man beispielsweise Apache Commons Logging als konkrete Logging-Implementierung nutzen, muss man nur das slf4j-jcl-VERSION.jar Modul zum Classpath hinzufügen. Solche Bindings werden nicht zur Compiletime benötigt und können daher in Maven per Runtime-Scope bzw. in Gradle als „RuntimeOnly“-Abhängigkeit angegeben werden. Da SLF4J intern das Java-SPI nutzt, muss keinerlei Code angepasst werden, um die konkrete Logging-Implementierung zu verwenden. Dieses Feature können wir nun für unsere Beispielanwendung nutzen:
Im Diagramm wird Log4J2 als Logging-Implementierung genutzt, und durch das Hinzufügen eines passenden Bindings werden alle Log-Nachrichten, die über den org.slf4j.Logger-Logger erstellt werden, automatisch an Log4J2 weitergereicht. Da in diesem Beispiel unsere „Database lib“ Abhängigkeit offenbar auch Log4J2 nutzt, werden so direkt die Nachrichten von verschiedenen internen und externen Modulen über Log4J2 abgehandelt. Neben den Bindings zu speziellen Logging Implementierung bietet SLF4J auch noch die Library slf4j-simple welche eine minimale Implementierung von SLF4J bietet und Nachrichten auf der Konsole (System.error) ausgibt.
Allerdings gibt es im Beispiel auch noch ein Problem: Die genutzte „Security lib“ benutzt Logback als Logger und dessen Nachrichten landen daher weiterhin in einer anderen Ausgabe. Für solche Fälle können sogenannte Adapter für SLF4J genutzt werden. Diese ermöglichen, dass Log-Nachrichten, die direkt zu einer Logging-API geschickt werden, an SLF4J weitergeleitet werden. Hierbei gibt es je nach Logging Framework völlig unterschiedliche Implementierungsansätze für solche Adapter. Während SLF4J einige solcher Adapter anbietet, werden ie auch teils von Logging-Frameworks direkt geliefert. Für Log4J2 muss beispielsweise folgende Abhängigkeit hinzugefügt werden, wenn man Nachrichten von Log4J2 an SLF4J weiterleiten möchte:
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-to-slf4j</artifactId>
Durch das Hinzufügen dieser Abhängigkeit, die man am besten nur zur Runtime zum Classpath hinzufügt, entsteht ein Verlauf des Logging wie in der folgenden Grafik :
SLF4J bietet für verschiedene Logging-Libraries eine gute Übersicht bezüglich der Integration durch Bindings und Adapter auf ihrer Webseite [5].
Wenn wir uns basierend auf den Erkenntnissen nun unsere Beispielanwendung anschauen, kann durch das Hinzufügen eines Adapters für Logback unser Ziel erreicht werden. Wie im folgenden Diagramm gezeigt, werden alle Log-Nachrichten des gesamten Systems über Log4J2 geleitet und wir haben somit den Vorteil, dass wir nur eine zentrale Stelle dokumentieren müssen.
URL dieses Artikels:
https://www.heise.de/-7355974
Links in diesem Artikel:
[1] https://www.heise.de/blog/Best-Practices-und-Anti-Pattern-beim-Logging-in-Java-und-anderen-Sprachen-7336005.html
[2] https://logging.apache.org/log4j/2.x/
[3] https://github.com/qos-ch/logback
[4] https://www.slf4j.org
[5] https://www.slf4j.org/legacy.html
[6] mailto:rme@ix.de
Copyright © 2023 Heise Medien
Kult oder Kultur? Apple's Vision Pro Konzept.
(Bild: apple.com)
Der Blog möchte nicht auf einen fahrenden Zug aufspringen, sondern erläutern, wie sich das Konzept der Vision Pro in industriellen Umgebungen nutzen lässt.
Auf der diesjährigen WWDC 2023 (World Wide Developer Conference) von Apple hat Tim Cook die seit Jahren "sagenumwobene" Vision Pro Brille vorgestellt. Auf YouTube finden sich hierzu viele Videos, weshalb sich dieser kurze Beitrag eine Wiederholung der dortigen Erkenntnisse spart.
Eines vorweg: es geht hier nicht primär um Apple und sein neuestes Produkt, sondern um die Betrachtung des zugrundeliegenden Konzepts und seiner möglichen Einsatzgebiete.
Aber etwas Kontext muss trotzdem sein: Jedenfalls soll die Vision Pro dank vieler Sensoren und Kameras sowie einer Auflösung von 4K für die beiden Bildschirme ein Augmented Reality der Zukunft bieten. Apple nennt dies bewusst Spatial Computing (räumliche Verarbeitung) statt auf überfrachtete Terminologie wie AR (Augmented Reality) oder MR (Mixed Reality) zurückzugreifen. So lassen sich im realen Raum virtuelle Hintergründe oder Umgebungen integrieren, in deren Kontext Anwender ihre Arbeits- oder Entertainment-Umgebung gestalten können. Der Grad an Virtualität ist also nach eigenem Gusto steuerbar.
Mit einem veranschlagten Preis von 3500 US-Dollar gehört das neue Gadget nicht gerade zu den Schnäppchen. Was aber viele übersehen: Apple sieht sich hier nicht im Wettbewerb mit Giganten wie Meta und deren Oculus-Produktfamilie, sondern adressiert eher das Marktsegment, das auch die 3200-US-Dollar teure Hololens von Microsoft abdeckt. Und das sind eher Businessanwender und vielleicht ein paar Technologie-Junkies mit großem Sparstrumpf. Ein Spielzeug für Gamer ist die Brille also nicht. Daher ist zu erwarten, dass Apps für die Vision Pro ebenfalls dieses Marktsegment adressieren und sich nicht unbedingt im unteren Preissegment tummeln.
Nach Ansicht des Autors könnte das Produkt gerade für geschäftliche und industrielle Umgebungen interessant sein. So erschließt das Konzept folgende Domänen und Anwendungsfälle:
Gerade die Mobilität der Brille erweist sich in diesen Szenarien als Vorteil, mal abgesehen von dem signifikanten Nachteil, dass der separat erhältliche Hochleistung-Akku gerade mal zwei Stunden durchhält. Natürlich stellt all das nur die Spitze des Eisbergs dar. Da noch niemand die Vision Pro in der Praxis prüfen konnte, gibt es viel Raum für reine Spekulation. Interessierte Entwicklungsschmieden und User sollten sich trotzdem schon jetzt ein paar Gedanken über mögliche Anwendungen machen.
All das führt natürlich unweigerlich zu Überlegungen hinsichtlich von Software- und Systemarchitektur der entsprechenden Systeme und Anwendungen. Auch die Kombination mit KI in einer Spatial-Computing-Anwendung könnte sinnvoll sein, etwa für die visuelle Einschätzung möglicher Fehlerursachen bei der Systemwartung.
Die Apple Vision Pro [1] ließe sich ergo als wichtiger Bestandteil neuer oder geänderter Anwendungsfelder etablieren. Ob die Konkurrenz dem etwas entgegensetzen will beziehungsweise kann, bleibt abzuwarten. Zumindest sollte das Konzept Entwickler und Anwender anregen, wie sie diese Art von Produkt sinnvoll und produktiv in ihren Domänen oder für sich selbst nutzen können. Die Zukunft beginnt jetzt.
URL dieses Artikels:
https://www.heise.de/-9181574
Links in diesem Artikel:
[1] https://www.heise.de/news/Apples-Vision-Pro-Apples-Mixed-Reality-Headset-enthuellt-9168785.html
[2] mailto:rme@ix.de
Copyright © 2023 Heise Medien
(Bild: Bogdan Vija / Shutterstock.com)
Wie kommuniziert ein Arduino-Board serverseitig mit einer REST API, um Sensor-Messungen über ein objektrelationales Mapping zu speichern?
Arduino-Boards, die über eine optionale Echtzeituhr (RTC = Real Time Clock) und ein WiFi-Modul verfügen, lassen sich ins Internet der Dinge integrieren, etwa über die Arduino Cloud oder AWS. Wer lieber On-premises agieren möchte, kann anders vorgehen und Server beziehungsweise Clients im heimischen Netz zur Verfügung stellen. Das bringt den Vorteil mit sich, immer die Kontrolle über die Bereitstellung (Deployment) zu behalten. Wie Entwicklerinnen und Entwickler dabei vorgehen können, zeigen dieser und der nachfolgende Artikel. Während zunächst die Server-Anwendung zur Sprache kommt, dreht sich der zweite Teil um die eingebetteten Clients.
Gleich vorweg: alle Implementierungsdateien liegen auf GitHub [1] parat. Eine gute Gelegenheit, um sich mühselige Handarbeit zu ersparen.
Im Beispielsszenario kommen ein Arduino Board mit C++-Sketch und ein Server mit Java und Spring Boot 3 zum Einsatz.
Die folgende Abbildung zeigt die Systemarchitektur:
Auf der linken Seite der Grafik ist das Arduino-Board, konkret ein Arduino Giga R1 WiFi, zu sehen. Verwendbar sind aber grundsätzlich alle Arduino-Boards oder Arduino-unterstützte Boards wie etwa ein ESP32. Am Arduino ist ein BME688-Sensor von Adafruit über I2C oder SPI angeschlossen. Der Arduino-Sketch macht periodisch Messungen von Temperatur, Feuchtigkeit, Luftdruck und Gaswiderstand, und verschickt das Ergebnis zusammen mit dem von der Echtzeituhr ausgelesenen Zeitstempel über einen POST-Aufruf an den Server: <hostname>:8080/measurement/api. <hostname> kann dabei die IP-Adresse oder der DNS- beziehungsweise Server-Name sein.
Serverseitig (rechte Seite in der Abbildung) fungiert eine in Java geschriebene Spring-Boot-3-REST-Anwendung als Kommunikationspartner für das Arduino-Board. Die zugehörige Datenbank läuft auf PostgreSQL, das die Anwendung als Docker-Container bereitstellt. So ist sichergestellt, dass Entwicklerinnen und Entwickler PostgreSQL auf dem eigenen Computer nicht extra installieren müssen.
Zur Laufzeit sendet das Arduino-Board POST-Nachrichten an den Server. Der dazugehörige REST-Endpunkt sorgt dann dafür, dass die Messung in die Datenbank übernommen wird.
Selbstverständlich lassen sich auch andere Datenbankmanagementsysteme nutzen, etwa mysql (maria). In diesem Fall müssten nur die Konfigurationsdateien application.yml sowie docker-compose.yml angepasst werden. Und in der Konfigurationsdatei pom.xml – das Projekt nutzt Maven für den Build-Prozess – sind entsprechend die PostgreSQL-Referenzenen durch die für die alternative Datenbank notwendigen Abhängigkeiten (<dependencies>) zu ersetzen. Wie aus folgender Abhängigkeits-Festlegung ersichtlich, gilt das nur für die zweite Abhängigkeit in Bezug auf PostgreSQL – in diesem Fall den pgJDBC-Treiber. Der soll übrigens nur zur Laufzeit aktiv sein, deshalb ist der benötigte Scope in der pom.xml-Datei mit runtime festgelegt. Alle anderen Abhängigkeiten beziehen sich auf Spring Boot, im Detail auf die Nutzung von Spring Web, Spring Data JPA (Java Persistence API) und Spring Test:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
Als Adapter nutzt die Server-Anwendung einen über die URL [2] bereitstehenden JDBC-Treiber.
Für den Fall, dass Entwickler und Entwicklerinnen bereits eine lokale Instanz von postgreSQL einsetzen, ist in der Konfiguration des Docker-Containers ein Port-Mapping definiert. Dieses verbindet den Server-Port 5332 mit dem Container-Port 5432 und verhindert somit Konflikte mit lokalen postgreSQL-Installationen, die auf Port 5432 lauschen.
Sobald der Arduino-Client einen POST-Aufruf mit einer Messung auf die Reise schickt, nimmt die Server-Schnittstelle die Messung als JSON-Body im jeweiligen REST-POST-Endpunkt entgegen und speichert die Messdaten in der postgreSQL-Datenbank measurement.
Die REST-Anwendung nutzt Port 8080, was sich in der Konfigurationsdatei application.yml bequem ändern lässt.
server:
port: 8080
#turn off the web server
spring:
datasource:
url: jdbc:postgresql://localhost:5332/measurement
username: michael
password: michael
jpa:
hibernate:
ddl-auto: create-drop
properties:
hibernate:
dialect: org.hibernate.dialect.PostgreSQLDialect
format_sql: true
show-sql: true
main:
web-application-type: servlet
Wer einfach loslegen will, startet nach Download der Quellen zunächst das Docker-Image. Dazu ist es erforderlich, auf dem eigenen Server beziehungsweise Desktop Docker zu installieren. Mittels der -> URL [3] bedeutet das zunächst, die entsprechende Docker-Implementierung für Linux, Windows oder macOS herunterzuladen und zu starten. In der Werkzeugleiste des Betriebssystems erscheint danach das für Docker typische Icon, ein Container-Frachtschiff.
Im nächsten Schritt sollten Entwickler und Entwicklerinnen die Konfiguration docker-compose.yml auf ihre Bedürfnisse anpassen:
services:
db:
container_name: postgres
image: postgres
environment:
POSTGRES_USER: michael
POSTGRES_PASSWORD: michael
PGDATA: /data/postgres
volumes:
- db:/data/postgres
ports:
- "5332:5432"
networks:
- db
restart: unless-stopped
networks:
db:
driver: bridge
volumes:
db:
Am Anfang der Konfiguration sind die bereitgestellten Dienste spezifiziert. Als Basis soll der Container das auf Docker Hub bereitgestellte Postgres-Image verwenden.
Im Bereich environment erfolgt die Festlegung den Anwendernamens und des zugehörigen Passworts sowie des Ordners im Container, der die PostgreSQL-Dateien enthalten soll. Das interne Netzwerk heißt db und ist über eine Bridge erreichbar. Stoppt der Container, löscht er die Datenbanktabelle.
Wer ein anderes DBMS nutzt, muss diese YAML-Datei entsprechend anpassen.
Im Ordner, in dem die YAML-Datei liegt, erfolgt nun der Start des Containers über
%docker compose up -d
Nach Eingabe von
%docker container ls
in der Kommandozeile müsste der Container auftauchen:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
d0cc2733c93a postgres "docker-entrypoint.s…" 4 hours ago Up 4 hours 0.0.0.0:5332->5432/tcp postgres
Damit ist es allerdings nicht getan, denn es fehlt noch die Datenbank für die Messungen.
Zunächst öffnen wir dazu eine Shell im Container für den interaktiven Zugriff:
docker exec -it postgres bash
In der Container-Session ist im Falle von postgreSQL das Kommando:
psql - U michael
notwendig, wobei U für den Nutzer steht. Ohne Änderungen ist im Beispiel michael als Benutzername und Passwort für postgreSQL voreingestellt.
Über psql lassen sich mit \l die existierenden Datenbanken abrufen.
Hier erscheint in etwa die folgende Ausgabe:
Name | Owner | Encoding | Collate | Ctype | ICU Locale | Locale Provider | Access privileges
-------------+---------+----------+------------+------------+------------+-----------------+---------------------
libc |
michael | michael | UTF8 | en_US.utf8 | en_US.utf8 | | libc |
postgres | michael | UTF8 | en_US.utf8 | en_US.utf8 | | libc |
template0 | michael | UTF8 | en_US.utf8 | en_US.utf8 | | libc | =c/michael +
| | | | | | | michael=CTc/michael
template1 | michael | UTF8 | en_US.utf8 | en_US.utf8 | | libc | =c/michael +
| | | | | | | michael=CTc/michael
(4 rows)
Um eine neue Datenbank zu erzeugen, geben wir
CREATE DATABASE measurement;
ein.
Achtung: Der Strichpunkt ist unbedingt erforderlich. Mit \c measurement verbinden wir uns mit der Datenbank und können danach mit \dt die vorhandenen Tabellen beziehungsweise Datenbankschemas analysieren. Noch ist dort nichts zu sehen, weil für das Datenbankschema die Spring-Boot-3-Anwendung sorgt. Spring Boot kreiert zu diesem Zweck selbständig folgendes SQL-DDL-Kommando:
create table measurement (
id integer not null,
date date,
humidity float(53),
pressure float(53),
resistance float(53),
temperature float(53),
time time,
primary key (id)
)
Wer eine professionelle IDE wie IntelliJ IDEA Ultimate nutzt, kann viele der beschriebenen und kommenden Schritte bequem über die IDE anstoßen.
Nun müssen wir noch die Java-basierte Spring-Boot-3-Anwendung starten (mittels Main.class), wobei wir eine ähnliche Ausgabe wie die folgende erhalten sollten – nur die letzten zwei Zeilen sind hier abgebildet.
…. noch viel mehr ….
2023-06-04T15:18:01.952+02:00 INFO 51585 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
2023-06-04T15:18:01.959+02:00 INFO 51585 --- [ main] de.stal.Main : Started Main in 3.172 seconds (process running for 3.807)
Es ist aus der Konsolenausgabe ersichtlich, dass Spring Boot für Webanwendungen automatisch den eingebetteten Web-Server Tomcat startet. Alternativ könnten wir auch Jetty nutzen, was folgende Konfigurationsänderung im pom.xml von Maven erfordert:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency><dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
In der Ausgabe befindet sich des Weiteren eine Warnung über Views, die wir in diesem Fall aber getrost ignorieren können.
Im Falle von gradle als Build-Tool wäre es stattdessen:
configurations {
compile.exclude module: "spring-boot-starter-tomcat"
}
dependencies {
compile("org.springframework.boot:spring-boot-starter-web:2.0.0.BUILD-SNAPSHOT")
}
Das aber nur am Rande, weil das Beispielsprojekt wie schon erwähnt Maven einsetzt.
Möchte man den Server auch ohne Arduino-Client testen, leistet die Anwendung Postman [4] hierfür gute Dienste. Sie ermöglicht das Aufrufen von APIs und benutzt curl als Basis dafür.
Das implementierte REST-Beispiel unterstützt folgende Aufrufe von REST-Endpunkten, um CRUD-Funktionalität (CRUD = Create Read Update Delete) bereitzustellen.
hostname:port/measurements/api, wobei im Body ein JSON-Objekt mit den Messergebnissen enthalten sein muss.hostname:port/measurements/api brauchen keinen Body im HTTP-Paket (none). Der Endpunkt liefert die Liste aller gespeicherten Messungen zurück. Das gilt auch für das Auslesen von individuellen Einträgen über hostname:port/measurements/api/42. In diesem Fall beziehen wir uns auf die Messung mit der Id 42.hostname:port/measurements/api/1 benötigt ein JSON-Objekt als Body, das die Werte des zum Datenbankeintrag gehörigen Primärschlüssels 1 aktualisiert. hostname:port/measurements/api/2 löscht den Eintrag mit dem Primärschlüssel 2 aus der Datenbank. Ebenso wie bei GET ist kein Body (none) vonnöten.Ein Beispiel für ein mitgeliefertes JSON-Objekt bei POST- oder PUT-Aufrufen könnte wie folgt aussehen:
{
“temperature“: 23.5,
“humidity“: 12.7,
“pressure“: 990.5,
“resistance“: 23.8,
"date“: “2023-06-10“,
“time“: “06:24:56“
}
Für die eigentliche Magie in der Server-Anwendung sorgt Spring Boot 3. Laut der Spring-Seite gilt für Spring Boot:
"Das Spring Framework bietet ein umfassendes Programmier- und Konfigurationsmodell für moderne Java-basierte Unternehmensanwendungen – auf jeder Art von Einsatzplattform.
Ein Schlüsselelement von Spring ist die infrastrukturelle Unterstützung auf der Anwendungsebene: Spring konzentriert sich auf das "Klempnerhandwerk" von Unternehmensanwendungen, sodass sich die Teams auf die Geschäftslogik auf Anwendungsebene konzentrieren können, ohne unnötige Bindungen an bestimmte Bereitstellungsumgebungen."
Das Java-Framework integriert einen Webserver mit Servlet-Unterstützung (Tomcat oder optional Jetty), über den es die REST-API im Web beziehungsweise Netzwerk zur Verfügung stellt. Die ganze Servlet-Maschinerie bleibt Entwicklerinnen und Entwicklern erspart. Zudem bietet es eine Repository-Schnittstelle, mit deren Hilfe die REST-Endpunkte neue Einträge beispielsweise speichern, ändern, löschen oder abrufen. Außerdem sorgt es über Annotationen für das objekt-relationale Mapping zwischen der measurement-Datenbanktabelle und dem entsprechenden Java-Objekt.
In der Datei Main.java (siehe Listing unten) ist die Klasse Main als @SpringBootApplication annotiert. Das sorgt dafür, dass Spring Boot nach Komponenten und Entitäten sucht und weitere Handarbeiten automatisch erledigt. Gleichzeitig fungiert die Klasse auch als @RestController, weshalb sich in ihr REST-Endpunkte befinden müssen. Mittels der Annotation @RequestMapping("measurements/api") legt man fest, welches Prefix jeder REST-Endpunkt bekommen soll. Externe Aufrufe beginnen dann immer mit diesem Prefix, also zum Beispiel GET <hostname>:8080/measurements/api. Der Konstruktur der Klasse Main enthält als Parameter ein MeasurementRepository, über das die REST-Endpunkte Aktionen auf dem aktuellen persistierten Objekt durchführen, etwa um eine neue Messung in der Datenbank zu speichern:
public Main(MeasurementRepository measurementRepository) {
this.measurementRepository = measurementRepository;
}
Die Schnittstelle MeasurementRepository erzeugt Spring Boot automatisch und übergibt sie per Dependency Injection an den Konstruktor. Es ist dementsprechend als @Repository definiert und leitet sich von JpaRepository ab. Die zwei Typparameter von JpaRepository beziehen sich auf die Persistenzklasse für Datenbankeinträge (Measurement) und auf den Datentyp des Primärschlüssels (Integer):
@Repository
public interface MeasurementRepository
extends JpaRepository<Measurement, Integer> {
}
Einer der definierten REST-Endpunkte ist etwa folgendes parametrisiertes GET:
@GetMapping("{id}")
public Optional<Measurement> getMeasurementById(@PathVariable("id") Integer id)
Die GetMapping-Annotation sorgt dafür, dass beim GET-Aufruf des Endpunkts mit der URL <hostname>:port/measurements/api/3 die Methode das entsprechende Datenbankobjekt mit Primärschlüssel 3 zurückliefert. Sie nimmt dabei das angesprochene MeasurementRepository zu Hilfe:
return measurementRepository.findById(id);
Wichtig in Zusammenhang mit REST-Endpunkten ist die Tatsache, dass bei Rückgaben von Ergebnissen Spring Boot dafür sorgt, diese zuvor in ein JSON-Objekt umzuwandeln – es wären im Übrigen auch andere Formate möglich. Zugleich erwartet jeder REST-Endpunkt, dass ihm entsprechende Messergebnisse im Body des HTTP-Pakets als JSON-Objekte übergeben werden. Das ist insbesondere bei POST und PUT notwendig, gilt aber nicht für in der URL angegebene Parameter wie etwa die gewünschte Id des "REST-Objektes" (siehe GET und DELETE). Letztere wird als Parameter in die URL integriert, zum Beispiel: localhost:8080/measurements/api/12
package de.stal;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Optional;
@RestController
@SpringBootApplication
@RequestMapping("measurements/api")
public class Main {
private final MeasurementRepository measurementRepository;
public Main(MeasurementRepository measurementRepository) {
this.measurementRepository = measurementRepository;
}
public static void main(String[] args) {
SpringApplication.run(Main.class, args);
}
@GetMapping
public List<Measurement> getMeasurement() {
return measurementRepository.findAll();
}
@GetMapping("{id}")
public Optional<Measurement> getMeasurementById(@PathVariable("id") Integer id){
return measurementRepository.findById(id);
}
@PostMapping
public void addMeasurement(@RequestBody NewMeasurementRequest request) {
Measurement measurement = new Measurement();
measurement.setDate(request.date);
measurement.setTime(request.time);
measurement.setTemperature(request.temperature);
measurement.setHumidity(request.humidity);
measurement.setPressure(request.pressure);
measurement.setResistance(request.resistance);
measurementRepository.save(measurement);
}
record NewMeasurementRequest(
java.sql.Date date,
java.sql.Time time,
Double temperature,
Double humidity,
Double pressure,
Double resistance
){}
@DeleteMapping("{measurementId}")
public void deleteMeasurement(@PathVariable("measurementId") Integer id) {
measurementRepository.deleteById(id);
}
// assignment
@PutMapping("{measurementId}")
public void updateMeasurement(@PathVariable("measurementId") Integer id,
@RequestBody NewMeasurementRequest msmUpdate) {
Measurement existingMeasurement = measurementRepository.findById(id)
.orElseThrow();
existingMeasurement.setDate(msmUpdate.date);
existingMeasurement.setTime(msmUpdate.time);
existingMeasurement.setTemperature(msmUpdate.temperature);
existingMeasurement.setHumidity(msmUpdate.humidity);
existingMeasurement.setPressure(msmUpdate.pressure);
existingMeasurement.setResistance(msmUpdate.resistance);
measurementRepository.save(existingMeasurement);
}
}
Die Klasse Measurement.java (siehe Listing unten) definiert die eigentliche Entität, die über ein objektrelationales Mapping mit der Datenbank verbunden ist. Dementsprechend enthält die Klasse eine @Entity-Annotation.
Wer IntelliJ IDEA oder eine andere fortschrittliche IDE nutzt, kann "Boilerplate"-Code wie Setters, Getters, toString(), equals(), hashcode() und Konstruktoren von der IDE generieren lassen. Andernfalls ist manuelles Eintippen nötig, was sich bei späteren Refactoring-Maßnahmen als umständlich erweist.
Nicht zu vergessen: Die Klasse Measurement benötigt einen parameterlosen Konstrukteur mit leerem Rumpf, damit das objektrelationale Mapping von Spring Boot aus JSON-Nachrichten Java-Objekte generieren kann.
Die Datenfelder der Klasse Measurement entsprechen den gewünschten Attributen des Datenbankeintrags, in unserem Fall Temperatur, Feuchtigkeit, Luftdruck, Gaswiderstand, Datum und Zeit. Den Primärschlüssel Integer id lassen wir Spring Boot für das Datenbanksystem automatisch generieren. Dazu dienen die Annotationen: @Id, @SequenceGenerator und @GeneratedValue. Der Primärschlüssel soll mit 1 starten und bei jedem neuen Datenbankeintrag um 1 hochgezählt werden. Die Annotation @id weist das gleichnamige Datenfeld als Primärschlüssel aus.
package de.stal;
import jakarta.persistence.*;
import java.sql.Date;
import java.sql.Time;
import java.util.Objects;
@Entity
public class Measurement {
@Id
@SequenceGenerator(
name = "measurement_id_sequence",
sequenceName = "measurement_id_sequence",
allocationSize = 1
)
@GeneratedValue(
strategy = GenerationType.SEQUENCE,
generator = "measurement_id_sequence"
)
private Integer id;
private Double temperature;
private Double humidity;
private Double pressure;
private Double resistance;
private java.sql.Date date;
private java.sql.Time time;
public Measurement(Integer id, Double temperature, Double humidity, Double pressure, Double resistance, Date date, Time time) {
this.id = id;
this.temperature = temperature;
this.humidity = humidity;
this.pressure = pressure;
this.resistance = resistance;
this.date = date;
this.time = time;
}
public Measurement() {
}
@Override
public String toString() {
return "Measurement{" +
"id=" + id +
", temperature=" + temperature +
", humidity=" + humidity +
", pressure=" + pressure +
", resistance=" + resistance +
", date=" + date +
", time=" + time +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Measurement that = (Measurement) o;
return Objects.equals(id, that.id) && Objects.equals(temperature, that.temperature) && Objects.equals(humidity, that.humidity) && Objects.equals(pressure, that.pressure) && Objects.equals(resistance, that.resistance) && Objects.equals(date, that.date) && Objects.equals(time, that.time);
}
@Override
public int hashCode() {
return Objects.hash(id, temperature, humidity, pressure, resistance, date, time);
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Double getTemperature() {
return temperature;
}
public void setTemperature(Double temperature) {
this.temperature = temperature;
}
public Double getHumidity() {
return humidity;
}
public void setHumidity(Double humidity) {
this.humidity = humidity;
}
public Double getPressure() {
return pressure;
}
public void setPressure(Double pressure) {
this.pressure = pressure;
}
public Double getResistance() {
return resistance;
}
public void setResistance(Double resistance) {
this.resistance = resistance;
}
public Date getDate() {
return date;
}
public void setDate(Date date) {
this.date = date;
}
public Time getTime() {
return time;
}
public void setTime(Time time) {
this.time = time;
}
}
Um die (Web-Server-)Anwendung zu konfigurieren, gibt es eine YAML-Datei mit dem Namen application.yml im resources-Ordner:
server:
port: 8080
#turn off the web server
spring:
datasource:
url: jdbc:postgresql://localhost:5332/measurement
username: michael
password: michael
jpa:
hibernate:
ddl-auto: create-drop
properties:
hibernate:
dialect: org.hibernate.dialect.PostgreSQLDialect
format_sql: true
show-sql: true
main:
web-application-type: servlet
Hier legen Entwickler und Entwicklerinnen unter anderem Port, Passwort und Benutzername fest, zudem die URL zum Zugriff auf die PostgreSQL-Datenbank. Benutzername und Passwort müssen mit der korrespondierenden Konfiguration in docker-compose.yml übereinstimmen. Mit ddl-auto weisen wir Spring Boot JPA an, die Tabelle measurement zu löschen, falls die Anwendung beziehungsweise Session terminiert (drop measurement). Über web-application-type: servlet bestimmt die Konfiguration, dass wir zum Bereitstellen unserer REST-API einen Webserver benötigen, der Java-Servlets unterstützt. Das können Tomcat oder Jetty sein.
Damit wären wir am Ende der notwendigen Java- und Konfigurationsdateien angekommen. Der Gesamtumfang aller Dateien liegt bei rund 250 Codezeilen zuzüglich Konfigurationsfestlegungen und inklusive der mittels IDE (IntelliJ IDEA) generierten Codes. Der Aufwand, um kleinere REST-APIs zu entwickeln, hält sich daher dank Spring Boot in Grenzen.
Durch Spring Boot fällt es leicht, recht schnell und effizient eine REST-API zusammenzubauen. Natürlich gäbe es auch Alternativen wie Quarkus oder Micronaut, aber Spring Boot ist sehr verbreitet und besitzt eine große Community. Das Beispiel ist für die Übergabe von Messungen des BME688 festgelegt. Es ist allerdings sehr leicht, die Anwendung für andere Sensoren oder andere Zwecke auszulegen.
Während sich dieser Beitrag auf die Server-Seite fokussiert hat, beschreibt der in Kürze nachfolgende Teil 2, wie sich auf Microcontroller-Boards wie dem Arduino Giga die dazu passenden REST-Clients erstellen lassen.
URL dieses Artikels:
https://www.heise.de/-9179764
Links in diesem Artikel:
[1] https://github.com/ms1963/RESTCommunication
[2] https://jdbc.postgresql.org/
[3] https://www.docker.com
[4] https://www.postman.com/
[5] mailto:map@ix.de
Copyright © 2023 Heise Medien
(Bild: Adafruit)
GPIO-Breakout-Boards wie das Adafruit FT232H ermöglichen den Zugriff auf elektronische Schaltungen von einem Computer.
Um von einem Computer aus auf Elektronikkomponenten zuzugreifen, existieren verschiedene Wege. Zum einen lassen sich Mikrocontroller über USB dazwischenschalten, um beispielsweise auf Arduino-Boards oder Raspberry Pi mittels einer auf den Boards laufenden Software die Elektronik zu kontrollieren. Für Arduino-kompatible Boards lässt sich zu diesem Zweck eine Firmata-Firmware installieren, die den mehr oder weniger direkten Zugriff auf Ports des Boards ermöglicht.
Eine weitere Option ist die RESTful-Kommunikation von einem Computer zu einem autonomen Mikrocontroller-Board, um über diesen Umweg auf Elektronik zuzugreifen. Das Programm auf dem Board fungiert als Vermittler zwischen Computer und Elektronik.
Ein dritter Weg besteht darin, ein sogenanntes GPIO-Breakout-Board über USB an den betreffenden Computer anzuschließen. In diesem Fall können Anwendungen direkt auf die am Breakout-Board befindlichen GPIO-Ports zugreifen. Zudem implementiert ein GPIO-Breakout-Board meistens Unterstützung für Protokolle wie SPI oder I2C. Dadurch ist auch das Ansteuern etwas komplexerer Hardware – zum Beispiel Displays und Sensoren – möglich.
Ein solches GPIO-Breakout-Board ist das Adafruit FT232H Breakout Board Blinka, das zu Straßenpreisen von rund 15 bis 18 Euro erhältlich ist, etwa bei BerryBase [1]. Es gibt natürlich auch andere GPIO-Boards, aber das Untersuchte besitzt eine ausreichende Zahl von GPIO-Ports und unterstützt zudem UART, I2C, SPI, Bit-Bang und JTAG. Entwicklerinnen und Entwickler können es über Python vom Computer aus ansteuern. Zudem sind für das FT232H-Board viele Informationsquellen verfügbar.
Für meine Experimente habe ich die neueste Variante des Boards erworben, die über einen USB-C-Port und einen Stemma-QT-Konnektor verfügt, der den Anschluss entsprechender Komponenten mit STEMMA/QT- beziehungsweise Qwiic-Interface erlaubt.
(Bild: Adafruit)
(Bild: Adafruit)
Das Board ist nach dem integrierten Chip benannt, einem FT232H von FTDI, der auch das komplette USB-Protokoll implementiert. Dadurch müssen sich Entwickler nicht mit systemnaher USB-Funktionalität herumschlagen. Im vielen Fällen ist keine Installation eines USB-Treibers auf dem Computer notwendig. Der FT232H unterstützt USB 2.0 Hi-Speed, was Übertragungsraten bis 480 MBit/s gewährleistet – im ebenfalls verfügbaren Full-Speed-Modus sind es stattdessen maximal 12 MBit/s. Die neue Boardversion besitzt einen USB-C-Port. Ein passendes Kabel findet sich nicht im Lieferumfang. Daher empfiehlt es sich, gleich ein entsprechendes Kabel mit zu bestellen, sofern nicht bereits vorhanden.
Der Chip-Kern arbeitet mit 1,8V, die Ein-/Ausgänge mit 3,3V. Letztere sind tolerant gegenüber 5V. Noch genauere Details finden sich auf dem Produktblatt des Chip-Herstellers [2].
Wie im Pinout-Diagramm ersichtlich (siehe obige Abbildungen), stellt die Adafruit-Lösung die GPIO-Ports D4-D7 sowie C0-C7 bereit.
Die Ports D0-D3 implementieren das SPI- oder das I2C-Protokoll. Das "oder" will sagen, dass zur Ansteuerung entweder I2C oder SPI verfügbar ist, aber nicht beides gleichzeitig.
Mit einem Schalter auf dem Board bestimmen Entwickler, ob sie I2C nutzen aber nicht SPI (Schalterstellung I2C auf on), oder ob sie SPI nutzen möchten aber nicht I2C (Schalterstellung I2C auf off). Im letzteren Fall gilt: D0 -> SCLK, D1 -> MOSI, D2 -> MISO, D3 -> CS0. Im ersteren Fall wiederum gibt es folgende Zuordnung: D0 -> SCL, D1 oder D2 -> SDA. Hierbei lässt sich einer der beiden Ports D1 und D2 nutzen, um das SDA-Signal von I2C zu übertragen. In der alten Version des Boards mussten Anwender für I2C noch den Port D1 mit dem Port D2 verdrahten, um dann D2 mit der eigentlichen Schaltung zu verbinden.
Zur Installation des Boards stellt Adafruit eine eigene Webseite mit Anleitung zur Verfügung [3]. Wichtig ist die Tatsache, dass die Programmierung auf dem Windows-, Mac- oder Linux-Computer eine Installation von Python 3 [4] und pip3 [5] erfordert.
Da Windows keine treiberlosen USB-Geräte unterstützt, müssen Anwender zunächst einen USB-Treiber von Zadig [6] installieren. Bei macOS und Linux ist das in den allermeisten Fällen nicht notwendig.
Danach folgen auf allen Betriebssystemen zum Zugriff über CircuitPython noch Installationen der libusb-Bibliothek sowie der speziellen Python-Bibliotheken pyftdi und adafruit-blinka – Blinka ist übrigens auch der Name des Breakout-Boards. Schließlich müssen noch udev-Rules (Linux only) festgelegt und eine Umgebungsvariable (alle Betriebssysteme) definiert werden. Diese Variable heißt BLINKA_FT232H und soll bei einem Wert von 1 das Vorhandensein eines Blinka-Boards signalisieren.
Wie bereits oben erwähnt, ist hier immer vorausgesetzt, dass auf dem Computer eine Installation von python3 und pip3 vorliegt.
Das Board wird übrigens ohne "vormontierte" Header ausgeliefert. Zunächst ist daher etwas Lötarbeit erforderlich.
Hier die verschiedenen Anleitungen:
Zur Ansteuerung von Schaltungen über das Breakout-Board Blinka muss dieses natürlich an den eigenen Computer angeschlossen sein. Softwaretechnisch dienen CircuitPython-Programme als Schnittstelle zum Board. Standard-Computer unter Windows, Linux, macOS verstehen zwar Python 3, aber wie können Entwickler CircuitPython nutzen? Dafür bedarf es zum einen der Blinka-Bibliothek für Python (adafruit-blinka) und entsprechender CircuitPython-Bibliotheken für die "ferngesteuerte" Hardware wie Sensoren, Bildschirme und dergleichen:
(Bild: Adafruit)
Da ich zum Test des Blinka-Boards einen Mac-Computer benutzt habe, folgen nun exemplarisch die für macOS notwendigen Installationsschritte im Detail:
Sollte noch keine Installation des Package Managers Homebrew vorhanden sein, installieren wir ihn mittels:
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
Anschließend ist die Installation von libusb mit Hilfe von Homebrew erforderlich:
brew install libusb
Danach die von pyftdi:
pip3 install pyftdi
Und im letzten Schritt das Installieren der eigentlichen Board-Bibliothek:
pip3 install adafruit-blinka
Nun sollte die Anwenderin die Umgebungsvariable BLINKA_FT232H mit 1 belegen:
export BLINKA_FT232H=1
Zu guter Letzt gilt es noch, die Python 3 REPL-Umgebung zu starten und schrittweise die folgenden zwei Anweisungen einzugeben:
import boarddir(board)
Verlief alles fehlerlos, gibt letztere Instruktion die Liste der verfügbaren Pins auf dem Terminal aus.
Fertig ist die Installation!
Zum Prüfen ob die Installation funktioniert, gibt es folgende Tests:
Zunächst ein Test, um sicherzustellen, dass das System das FT232H-Board erkennt:
from pyftdi.ftdi import Ftdi
Ftdi().open_from_url('ftdi:///?')
Normalerweise gibt es nur einen FTDI-Controller, weshalb die Ausgabe wie folgt aussehen könnte:
>>> from pyftdi.ftdi import Ftdi
>>> Ftdi().open_from_url('ftdi:///?')[Link auf https://learn.adafruit.com/circuitpython-on-any-computer-with-ft232h/setup] [11]
Available interfaces:
ftdi://ftdi:232h:1/1 ()/code/p
pcodePlease specify the USB device/code/p
pDanach eine Verifikation, ob die Umgebungsvariable vorhanden und korrekt gesetzt ist:/p
pcodeimport os/code/p
pcodeos.environ["BLINKA_FT232H"]/code/p
pDie Ausgabe müsste schlicht lauten:/p
pre class="rte__tx--listing"codestrong´1´/strong/code/pre
pWie gesagt, diese Beschreibung kratzt nur an der Oberfläche. Die genauen Anweisungen lassen sich a href="https://learn.adafruit.com/circuitpython-on-any-computer-with-ft232h/setup" rel="external noopener" target="_blank"strongauf der Anleitungs-Website von Adafruit nachvollziehen [12]/strong/a./p
pSollte es Probleme geben, finden sich auf der a href="https://learn.adafruit.com/circuitpython-on-any-computer-with-ft232h/troubleshooting" rel="external noopener" target="_blank"strongAdafruit-Website Post-Install-Checks [13]/strong/a entsprechende Hinweise./p
h3 class="subheading" id="nav_programmierung_3"Programmierung/h3
pIch spare mir Beispiele für das obligatorische LED-Blinken oder das Erkennen des Zustands eines Druckknopfes. Stattdessen soll der Anschluss eines a href="https://sensirion.com/de/produkte/katalog/SHT40" rel="external noopener" target="_blank"strongSensirion SHT40 [14]/strong/a als Beispiel dienen./p
pDieser Sensor kostet nur wenige Euro und misst Temperatur und Feuchtigkeit relativ präzise. Die typische Genauigkeit bei der relativen Luftfeuchtigkeit beträgt 1,8% RH und bei der Temperatur 0,2°C. Verwendung findet in diesem Zusammenhang ein Adafruit-Board mit I2C-Anschluss. Das Messen kann entweder mit oder ohne Heizen (-gt;codesht.mode/code) erfolgen. Für das Programmbeispiel habe ich auf das Heizen verzichtet. Da es sich um ein I2C-basiertes Sensorboard handelt, ist der I2C-Switch des FT232H auf ON zu stellen./p
figure class="a-inline-image a-u-inline"
div
img alt class="legacy-img " decoding="async" height="522" loading="eager" src="https://heise.cloudimg.io/width/696/q85.png-lossy-85.webp-lossy-85.foil1/_www-heise-de_/imgs/18/3/5/4/2/8/9/0/BildDerSchaltung-3584313b6978b464.JPG" srcset="https://heise.cloudimg.io/width/336/q70.png-lossy-70.webp-lossy-70.foil1/_www-heise-de_/imgs/18/3/5/4/2/8/9/0/BildDerSchaltung-3584313b6978b464.JPG 336w, https://heise.cloudimg.io/width/1008/q70.png-lossy-70.webp-lossy-70.foil1/_www-heise-de_/imgs/18/3/5/4/2/8/9/0/BildDerSchaltung-3584313b6978b464.JPG 1008w, https://heise.cloudimg.io/width/1392/q70.png-lossy-70.webp-lossy-70.foil1/_www-heise-de_/imgs/18/3/5/4/2/8/9/0/BildDerSchaltung-3584313b6978b464.JPG 2x" width="696"
/div
figcaption class="a-caption " p class="a-caption__text"
/pdiv class="text"An das Breakout-Board wird über ein Qwiic/STEMMA-QT-Kabel der Sensirion SHT40 angeschlossen./div
/figcaption
/figure
pDer Sensor benötigt nach jeder Messung etwa vier Sekunden Verschnaufpause – zwei Sekunden bei der Temperaturmessung und vier Sekunden bei der Feuchtigkeitsmessung. Deshalb enthält die while-Schleife ein codesleep(4)/code./p
pVor dem Programmstart müssen Entwickler noch die benötigte Bibliothek von Adafruit installieren:/p
pre class="rte__tx--listing"pip3 install adafruit-circuitpython-sht4x/pre
pDas vorliegende Programmbeispiel stammt im Original von Adafruit./p
h3 class="subheading"
a-code language="python"
pre class="rte__tx--listing listing"codeimport time # zum Verzögern der Ausgaben
import board # zur Ausgabe über FT232H
import adafruit_sht4x # Einbinden der Bibliothek für den Sensor
i2c = board.I2C() # Am I2C-Anschluss (SDA/SCL des FT232H) hängt ein Adafruit SHT40-Breakout-Board
sht = adafruit_sht4x.SHT4x(i2c)
print("SHT4X mit folgender Seriennummer entdeckt: ", hex(sht.serial_number))
sht.mode = adafruit_sht4x.Mode.NOHEAT_HIGHPRECISION # ohne Heizen!
# alternativ mit: sht.mode = adafruit_sht4x.Mode.LOWHEAT_100MS
print("Augenblicklicher Modus: ", adafruit_sht4x.Mode.string[sht.mode])
while True:
temperature, relative_humidity = sht.measurements
print("Temperatur: %0.1f C" % temperature)
print("Feuchtigkeit: %0.1f %%" % relative_humidity)
print("")
time.sleep(4) # 4 Sekunden Verzögerung/code/pre
/a-code
/h3
pDie Bildschirmausgabe gestaltet sich wenig spannend wie folgt:/p
figure class="a-inline-image a-u-inline"
div
img alt class="legacy-img " decoding="async" height="463" loading="eager" src="https://heise.cloudimg.io/width/696/q85.png-lossy-85.webp-lossy-85.foil1/_www-heise-de_/imgs/18/3/5/4/2/8/9/0/BildBildschirmausgabeBeispiel-59de146e93e58830.PNG" srcset="https://heise.cloudimg.io/width/336/q70.png-lossy-70.webp-lossy-70.foil1/_www-heise-de_/imgs/18/3/5/4/2/8/9/0/BildBildschirmausgabeBeispiel-59de146e93e58830.PNG 336w, https://heise.cloudimg.io/width/1008/q70.png-lossy-70.webp-lossy-70.foil1/_www-heise-de_/imgs/18/3/5/4/2/8/9/0/BildBildschirmausgabeBeispiel-59de146e93e58830.PNG 1008w, https://heise.cloudimg.io/width/1392/q70.png-lossy-70.webp-lossy-70.foil1/_www-heise-de_/imgs/18/3/5/4/2/8/9/0/BildBildschirmausgabeBeispiel-59de146e93e58830.PNG 2x" width="696"
/div
figcaption class="a-caption " p class="a-caption__text"
/pdiv class="text"Bildschirmausgabe mit den Messwerten des Sensirion SHT40./div
/figcaption
/figure
h3 class="subheading" id="nav_vorsicht_geboten_4"Vorsicht geboten/h3
pEine Warnung müssen Entwicklerinnen und Entwickler unbedingt berücksichtigen. Beim Anschluss von Komponenten an das Breakout-Board sollten sie sicherstellen, dass die jeweilige Schaltung keine Gefahr für den USB-Port darstellt. Ansonsten könnte eine Überlastung am USB-Port des Computers diesen in Mitleidenschaft ziehen. Adafruit weist darauf hin, dass das Board maximal 400-500mA aus dem USB-Port ziehen darf. Dafür gibt es diverse Abwehrmaßnahmen – darunter: Testen, die Absicherung gegenüber induktiver Lasten mittels Dioden, das Begrenzen von Stromstärken mittels Widerständen. Wer das vergisst, könnte den angeschlossenen Computer schädigen. Wie heißt es so schön: Vorsicht ist die Mutter der Porzellankiste./p
h3 class="subheading" id="nav_fazit_5"Fazit/h3
pGPIO-Breakout-Boards wie das Adafruit FT232H bieten eine sehr gute Möglichkeit, von Computern aus direkt auf Elektronik zuzugreifen, ohne ein Mikrocontroller-Board dazwischenschalten zu müssen. Das hat natürlich auch Grenzen hinsichtlich der möglichen Anwendungen. Beispielsweise erlauben Mikrocontroller-basierte Lösungen im Vergleich zu ihren PC-Pendants Echtzeitanforderungen und sind hinsichtlich der Verwendung verschiedener Protokolle wesentlich flexibler. Auf der anderen Seite besitzt eine Computer-gestützte Lösung ebenfalls Vorteile. Chip-Hersteller FTDI nennt unter anderem die Ansteuerung von Kameraschnittstellen, Bar-Code-Lesern, Settop-Boxen, Flash-Card-Lesern, MP3-Geräten sowie industrielle Anwendungen als mögliche Beispiele./p
pSchön wäre neben der Unterstützung für CircuitPython das Bereitstellen von C- beziehungsweise C++-Bibliotheken, um das Board auch in entsprechenden Anwendungen zu nutzen. Das ist allerdings Jammern auf hohem Niveau. Für die Arten von Anwendungen, die für einen FT232H in Frage kommen, reicht Python völlig aus. Speziell für das Rapid Prototyping eigener Experimente weist Python einige Vorteile auf./p
p
!-- RSPEAK_STOP --
!-- RSPEAK_START --
/p
hr
p
strongURL dieses Artikels:/strongbr
smallcodehttps://www.heise.de/-7091889/code/small
/p
p
strongLinks in diesem Artikel:/strongbr
smallcodestrong[1]/strongnbsp;https://www.berrybase.de/adafruit-ft232h-breakout-general-purpose-usb-zu-gpio-spi-i2c/code/smallbr
smallcodestrong[2]/strongnbsp;https://ftdichip.com/products/ft232hq//code/smallbr
smallcodestrong[3]/strongnbsp;https://learn.adafruit.com/circuitpython-on-any-computer-with-ft232h/setup/code/smallbr
smallcodestrong[4]/strongnbsp;https://www.python.org/downloads//code/smallbr
smallcodestrong[5]/strongnbsp;https://www.activestate.com/resources/quick-reads/how-to-install-and-use-pip3//code/smallbr
smallcodestrong[6]/strongnbsp;https://zadig.akeo.ie//code/smallbr
smallcodestrong[7]/strongnbsp;https://learn.adafruit.com/circuitpython-on-any-computer-with-ft232h/windows/code/smallbr
smallcodestrong[8]/strongnbsp;https://learn.adafruit.com/circuitpython-on-any-computer-with-ft232h/linux/code/smallbr
smallcodestrong[9]/strongnbsp;https://learn.adafruit.com/circuitpython-on-any-computer-with-ft232h/mac-osx/code/smallbr
smallcodestrong[10]/strongnbsp;https://cdn-learn.adafruit.com/downloads/pdf/circuitpython-on-any-computer-with-ft232h.pdf/code/smallbr
smallcodestrong[11]/strongnbsp;https://learn.adafruit.com/circuitpython-on-any-computer-with-ft232h/setup/code/smallbr
smallcodestrong[12]/strongnbsp;https://learn.adafruit.com/circuitpython-on-any-computer-with-ft232h/setup/code/smallbr
smallcodestrong[13]/strongnbsp;https://learn.adafruit.com/circuitpython-on-any-computer-with-ft232h/troubleshooting/code/smallbr
smallcodestrong[14]/strongnbsp;https://sensirion.com/de/produkte/katalog/SHT40/code/smallbr
smallcodestrong[15]/strongnbsp;mailto:map@ix.de/code/smallbr
/p
p class="printversion__copyright"
emCopyright © 2023 Heise Medien/em
/p
pstronga href="https://blockads.fivefilters.org"Adblock test/a/strong a href="https://blockads.fivefilters.org/acceptable.html"(Why?)/a/p
(Bild: Shutterstock)
Mit Java kann man Anwendung mittlerweile gut modularisieren, muss aber auch Abhängigkeiten beachten. Wenn diese keine Java-Module sind, wird es spannend.
In einem vorherigen Post habe ich über eine minimale Unterstützung für das Java Modulsystem (Java Platform Module System, JPMS) geschrieben und wie man helfen kann, sie zu erreichen. Nun kann es aber immer wieder passieren, dass Bibliotheken das Java-Modulsystem nicht unterstützen und es auch nicht absehbar ist, dass diese in Zukunft wenigstens als „Automatische Module“ verfügbar sein werden.
Wenn man seinen eigenen Code auf das JPMS umstellen will und von solchen Bibliotheken abhängig ist, muss man teilweise in die Trickkiste greifen. In diesem Post möchte ich einmal auf genau solche Abhängigkeiten eingehen und schauen, wie man damit umgehen kann.
Auch wenn ich mich eher bei Maven zu Hause fühle, habe ich erst neulich an der Umstellung eines großen Gradle-Projekts auf Java-Module gearbeitet. Da das Projekt Open Source ist, kann es einfach bei GitHub eingesehen werden [1]. In diesem Projekt gab es zu Beginn des Umbaus eine Vielzahl von Abhängigkeiten, die nicht das Java-Modulsystem unterstützt haben. Bei einigen konnten wir eine nachhaltige Lösung erzielen, indem wir direkt Pull Requests (PR) bei den jeweiligen Projekten erstellt haben, um diese um einen Automatic-Module-Name zu ergänzen. Wie man das einfach über ein Maven- oder Gradle-Plug-in erreichen kann, habe ich bereits im vorherigen Post zu dem Thema beschrieben. Ein Beispiel eines solchen PR kann hier [2] gefunden werden.
Nun gibt es aber auch Abhängigkeiten, bei denen man einen solchen PR nicht einfach stellen kann oder bei denen der PR nicht angenommen wird. Vielleicht hat man auch eine Abhängigkeit, deren Weiterentwicklung eingestellt wurde. In allen diesen Fällen wird eine andere Umsetzung benötigt. Im Grunde muss man sich selbst darum kümmern, aus den Abhängigkeiten Java-Module zu erstellen. Hierfür gibt es unterschiedliche Möglichkeiten. Man kann beispielsweise händisch einen Automatic-Module-Name-Eintrag zum Manifest des Jar hinzufügen und die abgeänderte Version dann in einem internen Maven Repository hosten. Für das erwähnte Gradle-Projekt haben wir auf das „extra-java-module-info“-Plug-in von Jendrik Johannes zurückgegriffen. Dieses als Open Source verfügbare Plug-in [3] erlaubt es, zur Build-Zeit einen Automatic-Module-Name Eintrag zu Abhängigkeiten hinzuzufügen. Konkret wird das Plug-in wie in folgendem Beispiel genutzt, wobei bei jedem automaticModule(…) Aufruf der Gradle Identifier der Abhängigkeit als erster Parameter und der zu nutzende Modulname als zweiter Parameter übergeben werden:
plugins {
id("org.gradlex.extra-java-module-info")
}
extraJavaModuleInfo {
failOnMissingModuleInfo.set(true)
automaticModule("io.prometheus:simpleclient",
"io.prometheus.simpleclient")
automaticModule("io.prometheus:simpleclient_common",
"io.prometheus.simpleclient_common")
automaticModule("io.prometheus:simpleclient_httpserver",
"io.prometheus.simpleclient.httpserver")
}
Zusammen mit dem Autor des Plug-ins konnten wir in einem sehr produktiven Austausch das Ganze sogar noch deutlich erweitern. Das Plug-in konnte neben automatischen Modulen schon immer Module mit einer module-info.java erstellen. Hierbei musste man allerdings händisch Angaben wie exports definieren. Durch neue Funktionalitäten kann man nun ein Modul so definieren, dass dessen vollständigen Packages exportiert werden (siehe weitere Infos [4]). Das hat den großen Vorteil, dass man nicht mit automatischen Modulen arbeiten muss, die einige Besonderheiten mich sich bringen, da unter anderem alle automatischen Module zu den „required“ Abhängigkeiten eines Moduls hinzugefügt werden, sobald ein automatischen Modul als „required“ in der module-info.java angegeben ist (siehe Java Spec [5]). Hier noch einmal ein großes Dankeschön an Jendrik Johannes als Maintainer der Bibliothek. Unsere Zusammenarbeit hat aus meiner Sicht extrem gut die Vorteile von Open Source aufgezeigt. Für alle, die hier noch tiefer einsteigen möchten, hat Jendrik mehrere Videos zu diesem Thema und anderen Themen rund um Gradle kostenlos auf YouTube gehostet [6].
Ein letztes großes Problem kann aber auch mit den hier vorgestellten Umsetzungen nicht behoben werden: Sobald ein JAR gegen die package split constraints des Java-Modulsystems verstößt, kann es nicht zum modulepath hinzugefügt werden. In diesem Fall müssen noch deutlich weitreichende Schritte vorgenommen werden. Diesen Punkt werde ich mich aber in einem zukünftigen Beitrag zuwenden.
URL dieses Artikels:
https://www.heise.de/-7536607
Links in diesem Artikel:
[1] https://github.com/hashgraph/hedera-services
[2] https://github.com/offbynull/portmapper/pull/48
[3] https://github.com/gradlex-org/extra-java-module-info
[4] https://github.com/gradlex-org/extra-java-module-info/issues/38
[5] https://docs.oracle.com/javase/specs/jls/se16/html/jls-7.html#jls-7.7.1
[6] https://www.youtube.com/@jjohannes
[7] mailto:rme@ix.de
Copyright © 2023 Heise Medien
(Bild: Pixels Hunter/Shutterstock.com)
Softwarearchitektur beeinflusst den Erfolg eines Projekts erheblich. Aber ausgerechnet "gute" Entwickler und Entwicklerinnen können Feinde der Architektur sein.
Warum ist Softwarearchitektur überhaupt so wichtig? Sie hilft uns, große Systeme zu implementieren. Durch die Strukturierung von Code in Module können wir sicherstellen, dass Entwicklerinnen und Entwickler für Änderungen nur das jeweils zu ändernde Modul im Detail verstehen müssen. Von anderen Modulen ist nur oberflächliches Wissen notwendig, beispielsweise über die Schnittstelle. Dieses Prinzip wird als "Information Hiding" bezeichnet: Details sind in den Modulen versteckt und können mit wenig Einfluss auf andere Module geändert werden. So lassen sich Module idealerweise in Isolation verstehen und ändern. Wenn die Architektur besonders gelungen ist, können Entwicklerinnen also mit wenig Wissen und daher besonders einfach Änderungen an der Software vornehmen.
Unter "guten" Developern möchten wir uns hier Personen vorstellen, die auch komplizierte Systeme verstehen und weiterentwickeln können. Solche Personen können auch mit Architektur-Fehlschlägen umgehen, bei denen Änderungen nicht so einfach sind und gegebenenfalls schwer zu verstehende Auswirkungen haben – manchmal auf völlig andere Stellen des Systems. Vielleicht finden sie es sogar intellektuell herausfordernd, sich mit solchen Problemen zu beschäftigen, und es macht ihnen Spaß. Außerdem sorgen diese Umstände nicht nur für einen sicheren Job, sondern auch für Prestige. Schließlich sind diese Personen oftmals die Einzigen, die wichtige Systeme noch ändern können und so für das Geschäft einen sehr einen hohen Wert haben.
Hinweise auf ein solches Verständnis von "guten" Entwicklern und Entwicklerinnen finden sich auch anderswo: Wer beispielsweise im Code die Feinheiten von einer Programmiersprache wie Java nutzt, die in einer Zertifizierung abgefragt werden, erstellt nicht besonders einfach zu verstehenden und zu ändernden Code, sondern gerade solchen, der die Features voll ausreizt und sich auf spezielle Eigenschaften der Sprache verlässt. Der Code ist besonders schwierig zu verstehen und damit zu ändern. Dennoch ist eine solche Zertifizierung als ein Hinweis auf das Können der Developer anerkannt. Wer also besonders unleserlichen Code schreiben oder lesen kann, gilt demnach als besonders gut.
Solche Entwickler und Entwicklerinnen mögen vielleicht "akademische" Architekturansätze nicht und bevorzugen eine "pragmatische" Herangehensweise. Schließlich profitieren sie von der Situation sogar. Bis zu einer gewissen Kompliziertheit funktioniert das ja auch. Bei "wirklich guten" Developern führt das bis zu einer erschreckend hohen Kompliziertheit. Sie verstehen dann Systeme, die für Außenstehende ein Mysterium sind. Dann fällt die Arbeit der Softwarearchitekten und -architektinnen aber vermutlich nicht auf fruchtbaren Boden. Schließlich kann man das System ja weiterentwickeln – warum es also besser strukturieren? In der Tat kann es passieren, dass die Architektin schließlich sogar das Team verlässt, um eine andere Position zu finden, wo ihre Mühen sinnvoller scheinen. Und daher sind "gute" Developer die natürlichen Feinde der Softwarearchitektur.
Das Problem ist das Verständnis, wann Entwicklerinnen als "gut" im Sinne von kompetent gelten. Natürlich müssen Entwickler Technologien verstehen. Es ist sicher vorteilhaft, wenn sie auch mit kompliziertem Code umgehen können, aber sie sollten solche Situationen vermeiden und auf keinen Fall auf sie hinarbeiten. Wirklich gute Developer arbeiten am liebsten an einfachem Code. Sie haben gerade eine Aversion gegen komplizierten Code – und dann haben Entwicklerinnen und "saubere" Architektur dasselbe Ziel.
Wir können in Projekten eine Umgebung schaffen, in der sich "saubere" Architektur durchsetzen kann. Dazu sollten wir Developer nicht beglückwünschen, weil sie eine komplizierte Änderung ganz alleine umgesetzt haben. Stattdessen sollte die Frage im Vordergrund stehen, wie sich solche komplizierten Änderungen in Zukunft vermeiden lassen und wie mehr Personen dazu befähigt werden können, solche Änderungen durchzuführen. Um die Entwicklung guter Softwarearchitektur zu fördern, sollten wir positive Arbeitsweisen belohnen und negative korrigieren. Statt den Fokus auf das individuelle Können eines Entwicklers beim Umgang mit kompliziertem Code zu legen, sollten wir uns auf die Qualität des Codes und die Umsetzung der Architektur konzentrieren.
Wichtig ist auch ein breites Interesse an Themen, die nicht mit den rein technischen Aspekten der Entwicklung zu tun haben. Das hilft dabei, die Domäne, die Anforderungen sowie die Nutzerinnen und Nutzer zu verstehen und die richtigen Features zu implementieren. Dazu muss man aber ein originäres Interesse an der Domäne und der Fachlichkeit haben – Personen mit einem breiten Interessenspektrum fällt das leichter.
Softwarearchitektur ist ein wichtiges Mittel, um die Kompliziertheit von Software einzudämmen. Developer, die auch mit komplizierten Systemen umzugehen vermögen, könnten einem solchen Architekturverständnis ablehnend gegenüberstehen.
URL dieses Artikels:
https://www.heise.de/-8971097
Links in diesem Artikel:
[1] mailto:sih@ix.de
Copyright © 2023 Heise Medien
(Bild: Shutterstock)
NullPointerExceptions sind mit die häufigste Fehlerquelle in Java. Durch statische Codeanalyse kann man diese Fehler deutlich minimieren.
Neue Projekte bringen neue Herausforderungen und neues Wissen mit sich. In meinem aktuellen Projekt [1] habe ich vor Kurzem eine Definition zum Umgang von null-Checks bei der statischen Codeanalyse erstellt. Vielen im Projekt war es wichtig, dass Parameter nicht nur zur Laufzeit beispielsweise durch Objects.requireNonNull(…)) überprüft werden, sondern auch direkt beim Kompilieren. Daher haben wir beschlossen, hier auch auf statischen Codeanalyse zur Überprüfung des Umgangs mit null zu setzen.
Bevor wir uns auf die verschieben Annotation und Checker Libraries stürzen, die es für Java gibt, möchte ich einmal kurz erläutern, worum es sich bei einer statischen Codeanalyse handelt. Hierbei wird der Programmcode beim Kompilieren durch ein Tooling überprüft. Das aktuell gängigste Tool in Java ist sicherlich SpotBugs, das sich in Builds mit Maven oder Gradle einbinden lässt und dessen Ergebnisse auch automatisiert auf Plattformen wie SonarCloud veröffentlicht werden können. Mit einer statische Codeanalyse kann man Probleme wie einen Speicherüberlauf, eine Endlosschleife oder "Out of Bound“-Fehler finden. Ein einfaches Beispiel ist eine Division durch 0. Sollte so etwas im Code vorkommen, kann die Analyse eine Warnung liefern oder je nach Konfiguration den kompletten Build fehlerhaft beenden. In unserem Projekt haben wir einen solchen Check in GitHub Actions, das die Ergebnisse direkt in SonarCloud [2] und einem Pull Request anzeigt.
Ein Problem beim Programmieren mit Java ist sicherlich der Umgang mit null. Wobei ich persönlich klar die Meinung vertrete, dass null seine Berechtigung hat, auch wenn Tony Hoare, der Erfindern der null-Referenz in der Programmierung, hierüber mittlerweile als ein „Billion-Dollar Mistake“ spricht
Allerdings kann man in Java nicht nativ definieren, ob ein Parameter null sein darf. Das hat man mittlerweile versucht, über verschiedene Mittel in der Klassenbibliothek zu lösen. Beispiele hierfür sind java.util.Optional, Objects.requireNonNull(…) oder auch JSR305 [3].
Ein anschauliches Beispiel für eine Programmiersprache, die eine native Unterstützung für null-Referenzen hat, ist Kotlin. Sie unterscheidet explizit zwischen nullable-Referenzen und nonnull-Referenzen. Hierbei sind Letztere der Standard, wobei einer Variablen mit einer solchen Referenz nie null zugewiesen werden kann. Benötigt man eine Variable, die null umfassen kann, muss man mit einer nullable-Referenz arbeiten. Diese wird über das ? Zeichen angegeben. Der folgende Code beinhaltet ein Kotlin Beispiel für beide Referenzen:
var a: String = "abc" // Regular initialization means
// non-null by default
a = null // compilation error
var b: String? = "abc" // can be set to null
b = null // ok
Da es eine solche native Unterstützung in Java nicht gibt, versucht man sie über statische Codeanalyse so gut wie möglich zu integrieren. Generell werden hier zwei Annotationen benötigt, wobei eine (@Nullable) definiert, dass ein Wert beziehungsweise eine Variable null sein kann, und die andere Annotation definiert, dass ein Wert oder eine Variable nie null sein darf (@NonNull).
Zum Verständnis soll ein Code Beispiel dienen, das eine Methode definiert und per Annotation die Information hinzufügt, dass der Rückgabewert der Methode nie null sein kann:
@NonNull String getName() {
if(isUnique()) {
return „Item „ + getId();
} else {
return null;
}
}
Wie man in der Implementierung der Methode sehen kann, ist es durchaus möglich, dass sie null zurückgibt. Das wär ein Fall, in dem die statische Codeanalyse eine Verletzung aufzeigt. Wer möchte, kann beispielsweise IntelliJ so konfigurieren, dass es solche Probleme direkt anzeigt.
Der folgende Code, der die @Nullable Annotation verwendet, führt zu einer Warnung in der Analyse:
void check(@Nullable String value) {
Objects.hash(value.toLowerCase());
}
In diesem Beispiel wird durch die Annotation @Nullable für die Variable value definiert, dass diese den Wert null haben kann. Dass der Code allerdings direkt auf die Variable zugreift, führt potenziell zu einer NullPointerException zur Laufzeit. Auch das würde durch die statische Codeanalyse ausgewertet und als Problem ausgegeben.
Wer eine solche statische Codeanalyse im eigenen Projekt integrieren möchte, muss ein paar einfache Voraussetzungen schaffen. Als Erstes muss man sich für eins oder mehrere Analysetools entscheiden. Hier empfehle ich Spotbugs [4], das der Nachfolger von Findbugs ist. Das Tool kann entweder über die Kommandozeile oder integriert in einen Gradle oder Maven Build gestartet werden. Um die gefundenen Probleme zu analysieren, kann man sich diese entweder im Spotbugs eigenen Swing-Client anschauen oder beispielsweise als HTML-basierte Übersicht als Bestandteil einer generierten Maven-Site mittels des Maven site-Ziels. Man kann das Tool so konfigurieren, dass es die Ergebnisse beispielsweise in ein Sonar beziehungsweise die SonarCloud hochlädt.
Wer @Nullable- und @NonNull-Annotationen im Projekt nutzen möchte, benötigt eine Library, die die Annotation bereitstellt. Das eigene Projekt muss nur zur Compile-Zeit von der Library abhängig sein. Auch hier gibt es (leider) eine ganze Fülle an Bibliotheken, die Annotationen bereitstellen. Die einzelnen Libraries basierend auf ihren Vor- und Nachteilen zu beleuchten, wird Bestandteil eines eigenen Posts sein. Daher empfehle ich zunächst Spotbugs Annotations als Abhängigkeit, die man unter den folgenden Maven-Koordinaten finden kann:
<dependency>
<groupId>com.github.spotbugs</groupId>
<artifactId>spotbugs-annotations</artifactId>
<version>4.7.3</version>
</dependency>
Die Fülle an Tools und Libraries macht es einem bedauerlicherweise nicht leicht, die perfekte und zukunftsorientierte Kombination zu finden. Als ich tiefer in das Thema eingetaucht bin, war ich erschrocken, dass vieles in diesem Bereich noch immer nicht durch Standards oder allgemein genutzte Best Practices definiert ist. Zwar gab es hier verschiedene Ansätze wie etwa mit demJSR305 [5], aber diese sind immer irgendwann im Sande verlaufen und werden heute teils in einem wilden Mix genutzt. Deswegen werde ich auch diesem Problem in naher Zukunft einen eigenen Post widmen.
URL dieses Artikels:
https://www.heise.de/-7351944
Links in diesem Artikel:
[1] https://github.com/hashgraph/hedera-services
[2] https://sonarcloud.io/project/overview?id=com.hedera.hashgraph%3Ahedera-services
[3] https://jcp.org/en/jsr/detail?id=305
[4] https://spotbugs.github.io
[5] https://jcp.org/en/jsr/detail?id=305
[6] mailto:rme@ix.de
Copyright © 2023 Heise Medien
(Bild: Andrey VP/Shutterstock.com)
Der Livestream bringt am Mittwoch um 18 Uhr spannende Einblicke und Hintergründe zur enterJS, interessante Gespräche mit Gästen und ein Gewinnspiel.
Auf dem YouTube-Kanal der the native web GmbH [1] findet alle drei Wochen – immer am Mittwochabend um 18 Uhr – ein Livestream zu verschiedenen Themen rund um die Softwareentwicklung statt. Meistens programmiere ich live, gelegentlich gibt es aber auch andere Formate wie ein Ask-me-Anything (AMA) oder Ähnliches.
Der nächste Livestream findet am Mittwoch, 19. April statt, und es gibt ein ganz besonderes Highlight: Er dreht sich um die enterJS-Konferenz [2], eine der im deutschsprachigen Raum bedeutendsten Veranstaltungen rund um JavaScript und Webentwicklung, die am 21. und 22. Juni 2023 in Darmstadt stattfinden wird.
Anlässlich des zehnjährigen Jubiläums erwarten wir interessante Gäste aus dem Programmbeirat der enterJS: Zum einen Maika Möbus, Redakteurin bei der iX und bei heise Developer, zum anderen Melanie Andrisek, Technikjournalistin und Lektorin im dpunkt.verlag. Beide werden spannende Einblicke in die Welt der JavaScript- und Webentwicklung sowie in die Organisation der enterJS geben.
Zum ersten Mal in unserer Livestream-Geschichte veranstalten wir außerdem ein Gewinnspiel. Zu gewinnen gibt es als Hauptpreis ein Freiticket für die enterJS 2023, ein iX-Plus-Abo sowie dreimal jeweils ein Buch aus dem Sortiment des dpunkt.verlag. Die Teilnahme wird ausschließlich während des Livestreams möglich sein, Voraussetzung sind ein Mindestalter von 18 Jahren und ein YouTube-Account.
Wir freuen uns auf Eure Teilnahme am Livestream am 19. April um 18 Uhr [4] und hoffen, dass Ihr genauso gespannt seid wie wir! Nutzt die Gelegenheit, um Eure Fragen und Kommentare im Livestream oder vorab auf Twitter [5] zu hinterlassen. Bis dahin, bleibt neugierig und wir sehen uns im Livestream!
URL dieses Artikels:
https://www.heise.de/-8964534
Links in diesem Artikel:
[1] https://www.youtube.com/@thenativeweb
[2] https://enterjs.de/
[3] https://www.heise.de/Datenschutzerklaerung-der-Heise-Medien-GmbH-Co-KG-4860.html
[4] https://www.youtube.com/watch?v=l2uZL4UXXUg
[5] https://twitter.com/thenativeweb
[6] mailto:rme@ix.de
Copyright © 2023 Heise Medien

(Bild: Peshkova/Shutterstock.com)
Die jährliche Befragung zu Jakarta EE läuft bis zum 25. Mai. Sie dient der Arbeitsgruppe als Richtlinie für die Weiterentwicklung der Spezifikation.
Jedes Jahr führt die Eclipse Foundation [1] beziehungsweise die Jakarta EE Working Group [2] eine große Umfrage durch, um Informationen zur Nutzung und Verbreitung von Jakarta EE zu sammeln. Diese Daten helfen der Arbeitsgruppe, die Nutzer von Jakarta EE, deren Bedürfnisse und Probleme besser verstehen zu können und sinnvolle Weiterentwicklung und Prioritäten für die Zukunft zu definieren.
Die Ergebnisse der Umfrage bleiben aber nicht nur innerhalb der Arbeitsgruppe, sondern sind als aufbereitetes PDF verfügbar [3]. Die Eclipse Foundation arbeitet die Zahlen grafisch auf. Beispielsweise zeigt folgendes Diagramm aus der letztjährigen Befragung die Beliebtheit der unterschiedlichen Application Server:

Auch 2023 gibt es wieder eine Umfrage, die bis zum 25. Mai online verfügbar ist [4]. Hierbei bitten wir jeden Entwickler und jede Entwicklerin an der Umfrage teilzunehmen, um ein möglichst diverses und breites Bild der Community zu erhalten.
URL dieses Artikels:https://www.heise.de/-8006410
Links in diesem Artikel:[1] https://www.eclipse.org[2] https://jakarta.ee[3] https://outreach.eclipse.foundation/jakarta-ee-developer-survey-2022[4] https://www.surveymonkey.com/r/63FSHRM[5] mailto:rme@ix.de
Copyright © 2023 Heise Medien
(Bild: insta_photos/Shutterstock.com)
Meetings nerven! Also: abschaffen! Aber: Kommunikation ist wesentlicher Teil der Softwareentwicklung und Meetings ein wichtiges Kommunikationswerkzeug. Was nun?
Auslöser dieses Blog-Posts ist eine Twitter-Diskussion [1]. Dort ging es um die Idee, bei einem Video-Call im Hintergrund eine Anzeige zu installieren. Sie soll basierend auf dem Gehalt beziehungsweise dem Tagessatz der Beteiligten anzeigen, wie viel Geld der Video-Call kostet.
Vermutlich geht es bei der Anzeige aber nicht nur um die Kosten. Video-Calls nerven Technikerinnen und Techniker und halten sie von der wirklichen Arbeit ab. Es gibt nämlich eigentlich nur einen Indikator dafür, ob wir etwas erreicht haben: lauffähiger Code. Diesen zu entwickeln, erfordert Konzentration. Ein Video-Call kostet nicht nur Zeit, sondern unterbricht auch die Konzentration. Es dauert lange, bis man wieder produktiv am Code arbeiten kann. Die negativen Auswirkungen von Meetings auf die Produktion von lauffähigem Code sind also noch schlimmer, als es diese Anzeige nahelegen würde.
Tatsächlich hilft die Anzeige aber nicht weiter. Schlimmer: Der Idee liegt eine falsche Wahrnehmung über Softwareentwicklung zugrunde. Nicht Code zu produzieren, ist unser größtes Problem. Das größte Problem ist es, die gemeinsame Arbeit an der Software zu koordinieren und die Anforderungen zu verstehen. Ein großes Optimierungspotential ist, Features korrekt zu implementieren und die richtigen Features zu implementieren. Kleine Unterschiede können schon massive Auswirkungen haben. Ein krasses Beispiel: Ein einziges zusätzliches Eingabefeld kann 90 % des Umsatzes kosten [2]. Es ist also kontraproduktiv, dieses Eingabefeld umzusetzen – egal wie schnell oder effizient das passiert. Der lauffähige Code mag einem zwar das Gefühl geben, dass man etwas erreicht hat, aber in Wirklichkeit war es ein Schritt in die falsche Richtung. Die beste Option wäre gewesen, diesen Code gar nicht zu schreiben. Den Code möglichst schnell und effizient zu schreiben, ist eine Scheinoptimierung.
Um zu verstehen, was zu entwickeln ist, muss man wohl Meetings abhalten. Schriftliche Anforderungen können nicht alle Informationen transportieren. Rückfragen, das gemeinsame Entwickeln von Ideen, Hinterfragen – das alles verlangt direkte Kommunikation und Meetings. Aber die Meetings nerven ja dennoch, und das Problem muss irgendwie angegangen werden. Die Kosten des Meetings explizit zu machen, ist naheliegend, weil es relativ einfach ist. Es ist aber die falsche Optimierungsgröße. Meetings, bei denen relevante Informationen ausgetauscht oder wichtige Entscheidungen getroffen werden, sind nützlich und nerven daher vermutlich nicht. Leider ist Nützlichkeit nicht ganz so einfach messbar, wie die Kosten. Dieses Problem des Fokus auf Kosten, weil sie leichter messbar sind, haben auch anderen Bereichen der IT, wie in einem anderen Blog-Post [3] diskutiert. Gerade weil die gemeinsame Arbeit die große Herausforderung in der Softwareentwicklung ist, sind Optimierungen an der gemeinsamen Arbeite sehr sinnvoll.
Also sollten wir die Qualität von Meetings verbessern. Beispielsweise kann man nach jedem Meeting die Frage stellen, wie hilfreich die Teilnehmenden das Meeting fanden. Oder man kann ein Keep / Try Feedback zur Verbesserung einholen: Was sollten man beim nächsten Meeting beibehalten (Keep)? Was kann man ausprobieren (Try)? Auch alle Meetings firmenweit schlicht abzusagen, wie Shopify dies getan hat [4], hat den Vorteil, dass vermutlich nur die Meetings wieder aufgenommen werden, die wertvoll sind. Es geht jedoch vielleicht zu weit, weil gegebenenfalls wichtige Meetings auch abgesagt werden.
So oder so ist eine Bewertung und kontinuierliche Verbesserung von Meetings ein konstruktiver Weg, um den Wert der Meetings zu maximieren. Und daher sollten wir den Wert von Meetings maximieren - und nicht etwa die Anzahl oder die Kosten.
Das Problem der Softwareentwicklung ist die Koordination und die Arbeit an den richtigen Herausforderungen. Dafür sind Meetings notwendig. Also sollten wir Meetings nicht einfach abschaffen, sondern den Wert maximieren.
URL dieses Artikels:
https://www.heise.de/-7549813
Links in diesem Artikel:
[1] https://twitter.com/ewolff/status/1574378678796144640
[2] https://ai.stanford.edu/~ronnyk/2009controlledExperimentsOnTheWebSurvey.pdf
[3] https://www.heise.de/blog/IT-Projekte-Kostenfaktor-statt-Wettbewerbsvorteil-6007620.html
[4] https://www.heise.de/news/Schluss-mit-Gruppenmeetings-Shopify-holt-zum-Kalender-Kahlschlag-aus-7448427.html
[5] mailto:rme@ix.de
Copyright © 2023 Heise Medien
(Bild: fizkes/Shutterstock.com)
Stellenanzeigen gleichen sich üblicherweise wie ein Ei dem anderen, statt den ersten individuellen Eindruck zu vermitteln, den man von einem Unternehmen erhält.
Wer sich heutzutage auf eine Stelle in der IT bewirbt, bekommt in der Regel über kurz oder lang ein PDF zu Gesicht, in dem die ausgeschriebene Position näher umrissen wird. Vergleicht man mehrere dieser Stellenausschreibungen, fällt auf, dass sie einander so stark ähneln, dass kaum ein Unterschied zwischen zwei Unternehmen auszumachen ist.
Abgesehen von etlichen Jahren technischer Expertise und einem abgeschlossenen Hochschulstudium oder einer vergleichbaren Ausbildung werden üblicherweise Teamfähigkeit, Flexibilität und ähnliche nicht näher definierte "Werte" gefordert.
Geboten wird im Gegenzug die Mitarbeit in einem mehr oder weniger jungen und dynamischen Team in einem spannenden Umfeld, eine Monatskarte für den ÖPNV, Vergünstigungen im ortsansässigen Fitnessstudio oder dem Supermarkt, und ein täglich frisch gefüllter Obstkorb.
Das ist das, womit Unternehmen im 21. Jahrhundert glauben, sich von anderen Unternehmen abheben zu können. Weil aber alle Unternehmen dieser Meinung sind, gleichen die Stellenausschreibungen abgesehen von Unternehmensname und -logo wie ein Ei dem anderen. Mit anderen Worten: Sie sind praktisch austauschbar.
Für die Unternehmen ist das in verschiedener Hinsicht kritisch. Zum einen verpassen sie bereits die erste Möglichkeit, einen individuellen Eindruck zu hinterlassen. Stattdessen bleibt hängen, dass anscheinend kaum ein Unternehmen verstanden hat, dass heutzutage mehr gefordert werden darf als eine lieblos in PDF-Form gebrachte Zeitungsanzeige, die inhaltlich und stilistisch aus den 90er-Jahren stammt.
Zum zweiten führen derartige Stellenausschreibungen aber auch dazu, dass sich potenzielle Bewerberinnen und Bewerber kein adäquates Bild von einem Unternehmen machen können – und sich dann entweder nicht oder auf unpassende Jobs bewerben. Das erste Szenario ist schade, weil ein Mensch und ein Unternehmen, die gut zusammenpassen würden, nicht zueinanderfinden. Das zweite ist lästig, weil es letztlich Zeitverschwendung ist, und zwar für beide Parteien.
All das habe ich im Juli 2022 auf YouTube in einem Video mit dem Titel Stellenanzeigen sind alle gleich (schlecht) [1] thematisiert. Gleichzeitig habe ich aber auch einen Vorschlag gemacht, wie eine Alternative aussehen könnte.
Denn ob es letztlich zwischen einer Bewerberin oder einem Bewerber auf der einen Seite und einem Unternehmen auf der anderen langfristig passt, hängt nur sehr bedingt von Technologien ab: Diese wandeln sich ohnehin alle paar Jahre, und stetige Weiterbildung ist in der IT daher unabdingbar. Zudem lässt sich die technische Eignung relativ schnell ermitteln.
Ob sich beide Parteien miteinander wohlfühlen, hängt auch nicht von einer ÖPNV-Fahrkarte oder einem Obstkorb ab. Beides sind natürlich nette Schmankerl, aber kaum jemand dürfte allein davon die Wahl des künftigen Arbeitgebers abhängig machen.
Viel wichtiger und grundlegend entscheidend ist hingegen, ob es ein gemeinsames Wertesystem gibt und ob die Chemie stimmt: Ein Unternehmen kann technologisch noch so passend sein und die denkbar tollsten Benefits anbieten – wenn es grundlegend unterschiedliche Überzeugungen gibt, wird man auf Dauer kaum gemeinsam glücklich werden.
Das ist aber unter Umständen schwierig herauszufinden. Ob man technologisch zueinander kompatibel ist, ist eine Sache von wenigen Sätzen: Entweder kann man sich auf einen gemeinsamen Nenner einigen oder nicht. Wichtiger ist das Drumherum: Entwicklerinnen oder als Entwickler, denen etwa das Testen wichtig ist, werden nicht in einem Unternehmen glücklich werden, das Tests als lästig ansieht.
Doch wie ein Unternehmen "tickt", das merkt man unter Umständen erst nach Wochen oder Monaten. Theoretisch ließe sich das auch bereits im Bewerbungsgespräch thematisieren, immerhin gibt es dafür in der Regel die berühmten abschließenden zehn Minuten, in denen es heißt: "Haben Sie denn noch Fragen an uns?"
Die Erfahrung zeigt, dass (zu) viele Bewerberinnen und Bewerber oftmals nicht wissen, was sie in dieser Situation fragen sollen – und wenn man wirklich vorbereitet ist und viele Fragen mitbringt, reicht die Zeit in den seltensten Fällen aus, weil Unternehmen nicht darauf eingestellt sind, dass jemand tatsächlich mehr als zwei Fragen stellt.
Wie könnte also eine Alternative zu den heutigen Stellenausschreibungen aussehen?
Statt zu versuchen, sich auf mehr oder weniger formale Art in einem Dokument mit ein paar Schlagworten auszuzeichnen, könnten Unternehmen stärker auf einen persönlichen und vor allem authentischen Austausch zu setzen. Das Problem dabei ist allerdings, dass das im Vorfeld schwierig zu gestalten ist, denn man möchte ja bereits im Vorfeld sicherstellen, dass Bewerberinnen und Bewerber einen weitaus besseren Eindruck erhalten, und sich dann viel zielgerichteter bewerben können – was nicht nur die Chancen für beide Seiten erhöht, sondern auch allen Beteiligten Zeit spart.
Was also tun?
Aus dieser Frage heraus ist die Idee entstanden, dass ich mich selbst (quasi stellvertretend) bei Unternehmen "bewerbe", wir davon ausgehen, dass wir technologisch zueinanderpassen, und wir dann ein sehr ausführliches Gespräch führen, in dem ich all meine Fragen stellen kann. Im Prinzip also eine nachgestellte Bewerbungssituation, die dabei (natürlich mit dem Einverständnis aller Beteiligten) auf Video aufgezeichnet wird.
Dieses Format haben wir im vergangenen Jahr bereits einige Male durchgeführt, und auf dem Weg sind drei Videos entstanden:
Auch wenn die Situation in den Videos nicht ganz einer echten Bewerbung entspricht, zeichnet ein solches Gespräch doch ein deutlich genaueres Bild von dem jeweiligen Unternehmen als es ein knappes PDF je könnte.
Hoch anzurechnen ist den beteiligten Unternehmen aus meiner Sicht vor allem, dass sie sich auf dieses Experiment eingelassen und an dem Format teilgenommen haben, was – wie man hört – auch von Erfolg gekrönt war: Die ein oder andere Einstellung hat es bedingt durch diese Videos bereits gegeben.
Wir möchten dieses Format auch in diesem Jahr fortsetzen. Das heißt: Wer ein Unternehmen vertritt, das auf der Suche nach weiteren Mitarbeiterinnen und Mitarbeitern ist, darf sich gerne bei mir melden. Die Kontaktmöglichkeiten finden sich unter diesem Video [5].
Viel wichtiger jedoch: Wer auf der Suche nach einem neuen Job ist oder jemanden kennt, für die oder den eine der genannten Stellen interessant sein könnte, kann sich gerne das jeweilige Video anschauen beziehungsweise weiterleiten. Vielleicht gelingt es uns auf dem Weg gemeinsam, die Situation mit den Stellenanzeigen zumindest etwas zu verbessern.
URL dieses Artikels:
https://www.heise.de/-7544246
Links in diesem Artikel:
[1] https://www.youtube.com/watch?v=Ub0oEmv147A
[2] https://www.youtube.com/watch?v=G9VAtkKS7ZE
[3] https://www.youtube.com/watch?v=_PgZQgzVSIE
[4] https://www.youtube.com/watch?v=8njAGQBpN_0
[5] https://www.youtube.com/watch?v=Ub0oEmv147A
[6] mailto:rme@ix.de
Copyright © 2023 Heise Medien

Gleisanlagen in Maschen
(Bild: MediaPortal der Deutschen Bahn)
In den letzten Jahren gab es verschiedene Veränderungen im Java Release Train, die zu Verwirrung und Missverständnissen in der Community geführt haben.
Bereits seid dem Release von Java 9 in 2017 werden neue Java-Plattform-Releases durch einen neuen und deutlich besser definierten Release Train veröffentlicht. Leider waren die Aussagen zu den verschiedenen Änderungen und Verbesserungen des Release Train oft nur sehr vage und teils gab es sogar falsche Gerüchte in der Community. Vor allem Oracle hat hier eine schlechte Pressearbeit geliefert und den neuen Release Train nicht so transparent und sauber beschrieben, wie es hätte sein müssen. Da mit dem Release von Java 17 erneut Änderungen in den Release Train eingeflossen sind, möchte ich diesen Post nutzen, um den konkreten Ablauf zu beschreiben.
Bevor wir aber auf den neuen Release Train schauen, ist es sinnvoll, einmal auf die Releases von alten Java-Versionen zu blicken. Das folgende Diagramm zeigt den Release-Zeitpunkt und die Lebensdauer (Lifecycle) von Java 6, 7 und 8. Der Lifecycle der Versionen ist hierbei durch die Verfügbarkeit von kostenlosen Security-Updates der jeweiligen Version definiert. Sobald es für eine der Versionen keine kostenlosen Updates mehr gab oder gibt, endet im Diagramm deren Lebensdauer.

Im Diagramm kann man unterschiedliche Punkte erkennen. Wichtig ist erst einmal, dass immer ein Zeitraum existiert, in dem noch wenigstens zwei Releases mit Security-Updates unterstützt werden. Diese Zeit dient Usern der Plattform als Zeitspanne zur Migration ihrer Anwendungen auf die jeweils neuere Java-Version. Hinzu kommt, dass die Abstände der Releases von neuen Versionen unterschiedlich sind. Das liegt daran, dass diese Java-Versionen veröffentlicht wurden, sobald alle für die jeweilige Version definierten Features implementiert wurden.
Für jede dieser alten Java-Versionen wurden die zu implementierenden Features innerhalb eines Java Specification Requests (JSR) im Java Community Process (JCP) definiert. Für Java 8 kann man beispielsweise unter JSR 337 [1] in Sektion 3 eine Liste der einzelnen Features finden. Diese sind hierbei wiederum auch als JSRs spezifiziert, wobei die Einführung von Lambda-Ausdrücken in Java im JSR 335 und die Date & Time API in JSR 310 definiert wurde.
Da im Vorfeld fest definierte wurde, welche JSRs in einer neuen Java-Version umgesetzt werden sollen, konnte der Release-Zeitpunkt nie fest definiert werden. Das hat teils zu langen Zeitspannen zwischen den Veröffentlichungen der Versionen geführt.
Bei dem gesamten Prozess ist es noch wichtig anzumerken, dass diese Releases keine Versionen sind, die beispielsweise für das Oracle-JDK spezifisch sind. Alle diese Versionen werden im OpenJDK erstellt. Somit kann jeder Java-Vendor einen Build seiner Runtime mit der aktuellen Version anbieten, ohne dass es Unterschiede in der Funktionalität gibt.
Mit Java 9 hat sich diese Vorgehensweise aber stark verändert. Statt langlebiger Releases mit einem unbestimmten Erscheinungsdatum und Lebenszyklus erscheint nun alle sechs Monate ein neues Major Release der Java-Plattform. Das passiert wie bisher im OpenJDK, aber die Sourcen des OpenJDK sind seit Java 17 komplett zu GitHub migriert, wo man die genauen Workflows deutlich besser nachvollziehen kann. Da der Veröffentlichungszeitpunkt eines Release fest definiert ist, können die Features nicht mehr im Vorfeld bestimmt werden. Viel mehr enthält eine neue Java Version alle zu diesem Zeitpunkt abgeschlossen Features. Diese Features werden als JDK Enhancement Proposals (JEP) definiert und können alle auf der Webseite des OpenJDK eingesehen werden [2]. Diese JEPs kann man sich hierbei ähnlich zu Epic Issues vorstellen. Ab Java 10 kann man dann auch auf der jeweiligen Seiten des Releases die Liste der im Release enthaltenen JEPs sehen (siehe Beispiel für Java 11 [3]).
Da manche diese Features längere Zeit zum Entwickeln benötigen und starke Auswirkung auf die Java-Plattform haben, wurde für JEPs der Preview- und Incubator-Status eingeführt. Neue APIs können durch Letzteren bereits zum Testen in Java-Versionen landen, bevor sie in einer künftigen Version finaler Bestandteil der Java-Klassenbibliothek werden. Solche APIs liegen in einem speziellen Incubator Package und werden erst mit ihrem finalen Release an ihr korrektes Package verschoben. Der Preview-Status kann genutzt werden, um neue Sprachfeatures der Java-Plattform bereits im Vorfeld verfügbar zu machen. Solche Features muss man über einen Kommandozeilenparameter aktivieren:
$ javac HelloWorld.java
$ javac --release 14 --enable-preview HelloWorld.java
$ java --enable-preview HelloWorld
Mit einem neuen Release alle sechs Monate hat sich auf die Lebensdauer von Java-Versionen deutlich verändert. Hierbei wird nun zwischen Long-Term Support (LTS) und normalen (non LTS) Versionen unterschieden. Letztere haben eine Lebensdauer von genau sechs Monaten bis zum Release der nächsten Version. So hatte Java 14 eine Lebensspanne von März 2020 bis September 2020. Als LTS gekennzeichnete Versionen werden im OpenJDK länger gepflegt und erhalten über einen längeren Zeitraum Sicherheitsupdates.
Für das LTS-Release Java 17 wird es nach heutigem Kenntnisstand bis 2027 Updates geben. Eine gute Übersicht der LTS Versionen bietet Eclipse Adoptium in der Supportübersicht [4]. Auch wenn LTS-Updates keine neuen Features enthalten, fixen sie doch alle bekannten Sicherheitslücken. Daher heißen solche Releases auch Critical Patch Update (CPU). Für Java-Versionen, die nicht als LTS-Versionen definiert sind, erscheinen zwei geplante CPU-Releases. So wurde für Java 16 nach dem ersten Release im März 2021 die Version 16.0.1 im April 2021 und 16.0.2 im Juli 2021 als CPU veröffentlicht. Mit dem Erscheinen von Java 17 wurde festgelegt, dass es alle zwei Jahre ein neues LTS Release von Java geben wird. Hierdurch wird Java 21 im September 2023 das nächste LTS-Release sein.
Basierend auf diesen Definitionen sieht der Release-Graph der Java Versionen seid Java 9 folgendermaßen aus:

Da alle Arbeit im OpenJDK erfolgt, kann der Veröffentlichungszeitpunkt von einzelnen Java Distributionen leicht abweichen. Sobald das Release im OpenJDK freigegeben ist, startet die Arbeit zum Erstellen der Distributionen wie Eclipse Temurin, Oracle JDK oder Azul Zulu. Hierbei ist jedem Hersteller freigestellt, die Sourcen des OpenJDK um zusätzliche Funktionen zu erweitern, um sich durch Alleinstellungsmerkmal abzuheben. Manche wie Azul oder Bellsoft bündeln beispielsweise JavaFX in ihren Builds.
In früheren Versionen hat vor allem Oracle mit Tools wie WebStart oder Mission Control versucht, sich von anderen OpenJDK-Builds abzuheben. Das hat allerdings zu Kompatibilitätsproblemen in der Community geführt und wird zum Glück heute in der Regel nicht mehr praktiziert.
URL dieses Artikels:https://www.heise.de/-7350614
Links in diesem Artikel:[1] https://www.jcp.org/en/jsr/detail?id=337[2] https://openjdk.java.net/jeps/0[3] https://openjdk.java.net/projects/jdk/11/[4] https://adoptium.net/de/support/[5] mailto:rme@ix.de
Copyright © 2023 Heise Medien