Dass die JavaLand 2024 nicht mehr im Phantasialand stattfindet, eröffnet der Community-Konferenz neue Möglichkeiten.
Vor kurzem ist die Bombe geplatzt: Die JavaLand 2024 [1] kann doch nicht wie geplant im Februar am gewohnten Ort stattfinden. Stattdessen wird sie das erste Mal seit ihrem Bestehen an eine neue Location umziehen. Im April wird sich die Java-Community nächstes Jahr nicht mehr im Freizeitpark, sondern am Nürburgring treffen. Wir sind uns sicher, dass diese Neuigkeiten bei vielen Community-Mitgliedern Fragen aufwerfen, allen voran: Warum geht ihr von dieser coolen Location weg? Wir möchten diesen Blogbeitrag nutzen, um unsere persönliche Sichtweise darzustellen, auch wenn die offizielle Pressemitteilung schon einige Fragen beantwortet [2]. Uns ist die Community wichtig und hier können wir etwas konkreter werden. Vor allem möchten wir Gerüchten vorbeugen.
Wie viele aus der Community fanden auch wir das Phantasialand als Location einen Lichtblick im Einheitsgrau der Hotel-Lobbys, Kongresszentren und Kinosäle (wobei die schon auch cool sind). Aber die Location war nicht der einzige Punkt, denn die JavaLand hat dank uns allen (Sprechern, Besuchern, Java User Groups, Aussteller ...) noch einiges mehr zu bieten und hebt sich positiv aus der Konferenzlandschaft ab. Diesen ganz eigenen Spirit empfinden wir bis heute als einmalig und wollen ihn als Mitglieder der Konferenzleitung auch in Zukunft sichern und weiter ausbauen.
Aber die Hürden, die JavaLand im Phantasialand durchzuführen, waren zuletzt zu hoch geworden. Es hatte sich bereits in den vergangenen Jahren gezeigt, dass das Event-Management des Phantasialand nicht in der Lage ist, eine JavaLand nach den Wünschen unserer Community durchzuführen. Leider haben die vielen Bemühungen nicht ausgereicht, um die problematischen Punkte zu klären. Das Phantasialand-Management hat nicht flexibler agiert. Vielmehr wurde die Situation für uns in der Planung und Durchführung der Konferenz jedes Jahr schwieriger. Nächstes Jahr hätten diese Punkte auch das erste Mal alle Besucher direkt betroffen. Wir hätten eine JavaLand im kühlen Februar ohne Nutzung der Fahrgeschäfte gehabt. Dazu kam, dass die Konferenzleitung auch die Preiserhöhungen (inklusive der Hotelpreise) nicht mehr sinnvoll abfangen konnte, sodass dem Rückgang beim Spirit eine deutliche Erhöhung der Kosten pro Besucher gegenübergestanden hätte. Wir möchten hier nicht noch konkreter werden und auch ein Bashing gegenüber dem Phantasialand vermeiden. Allerdings sind wir der Meinung, dass diese offene Begründung nötig ist, um Gerüchte oder Fehlinformationen zu vermeiden. Am Ende unterscheidet sich unsere Java Community zum Glück grundsätzlich von der Klientel, die sich das Phantasialand Event-Management offenbar als Kunden wünscht.
Das Ganze führt uns nun zur aktuellen Situation und der Bekanntmachung der großen Änderungen für die JavaLand 2024. Und das dann auch noch zum 10. Jubiläum. Wird es die gleiche Konferenz sein wie bisher? Ganz bestimmt nicht. Aber gerade wir als agile Entwickler wissen ja, Änderungen gehören zu unserem Alltag. Sie bringen auch immer Fortschritt und ganz neue Möglichkeiten. Aktuell gibt es viele Diskussionen in der Konferenzleitung, wie wir den Kern des JavaLand-Spirit an die neue Location mitnehmen und durch weitere Aspekte sinnvoll ergänzen können. Der Vorteil ist, dass der Nürburgring bereits einiges an Attraktionen zu bieten hat. Denkbar sind Rennen in E-Karts (uns ist auch ein gewisser Grad an Nachhaltigkeit wichtig) und es gibt zum Beispiel auch ein 3D Kino. Außerdem sprudeln bereits die Ideen, wie wir mit Ständen, Foodtrucks, großartigen Abend-Events mit Live-Musik und viele weiteren Attraktionen ein Festival erschaffen und aber auch das Themenpark-Feeling an den Ring holen können.
Hinzu kommt, dass die Location deutlich mehr Möglichkeiten für Community-Aktivitäten bietet. Daher möchten wir euch erneut aufrufen, Ideen für eines der Highlights der JavaLand, das Community-Programm einzureichen. Die Community-Aktivitäten reichen vom direkten Austausch in Diskussionsrunden über kleine Hands-On-Sessions, dem Netzwerken, die Durchführung der JavaLand4Kids bis zu sportlichen Aktivitäten. Das Joggen ist auf jeden Fall wieder dabei, aber vielleicht drehen wir diesmal auch ein paar schnelle Fahrrad-Runden auf dem Nürburgring. Wer mag, kann sich bei der Anreise der JavaLand Fahrrad-Sternenfahrt anschließen, wenn auch diesmal mit ein paar mehr Höhenmetern. Die im vergangenen Jahr erstmals angebotene Unkonferenz wird ebenfalls wieder am Vortag stattfinden. Und im normalen Vortragsprogramm wird es erstmals Deep-Dive-Sessions von fast zwei Stunden geben. All das wird die Black Mamba, die Taron und die anderen coolen Fahrgeschäfte nicht ersetzen können, aber euch wird garantiert nicht langweilig. Und jetzt ist es vielleicht auch leichter, der Chefin oder dem Chef den Besuch bei der JavaLand zu verkaufen.
Wir sind sehr froh über die Neuerungen und sehr gespannt auf die Möglichkeiten, die sich uns bieten werden. Die Planung der JavaLand-Konferenz, die zähen Monate mit den schwierigen Verhandlungen (und den trotzdem nur sehr eingeschränkten Möglichkeiten im Phantasialand) sowie die schwierige Suche nach Alternativen hat uns das erste Mal nicht mehr wirklich Spaß gemacht. Aber diese neue Location bietet nun wieder interessante Optionen, diese ganz besondere Konferenz noch ein Stück besser zu machen. Und hier bitten wir natürlich auch euch, die Java-Community, kreativ zu werden. Macht mit euren Vorschlägen zu den Community-Aktivitäten die JavaLand 2024 wieder zu einem ganz besonderen Erlebnis. Gerade die zehnte JavaLand möchten wir lieber an einem alternativen Ort mit neuen Aktivitäten und Ideen durchführen als unter den leider immer größer werdenden Einschränkungen im Phantasialand.
URL dieses Artikels:
https://www.heise.de/-9322819
Links in diesem Artikel:
[1] https://www.heise.de/news/JavaLand-Konferenz-aendert-den-Termin-und-zieht-zum-Nuerburgring-9317740.html
[2] https://www.javaland.eu/de/home/news/details/javaland-2024-neuer-termin-und-neue-location-call-for-papers-verlaengert/
[3] mailto:rme@ix.de
Copyright © 2023 Heise Medien
(Bild: Natalia Hanin / Shutterstock.com)
Mit dem OpenJDK 21 ist gerade ein Release erschienen, für das Hersteller längeren Support (LTS) anbieten. Diese Version hat aber auch sonst einiges zu bieten.
Mit 15 JEPs (JDK Enhancement Proposals) bringt das OpenJDK 21 so vielen Themen wie lange nicht. Und es hält auch inhaltlich eine Menge interessanter Features für uns Java-Entwickler bereit. Einige Funktionen sind schon länger in Arbeit, darunter das Pattern Matching, die Virtual Threads, die Vector API oder auch die Foreign Function & Memory API. Während beim Pattern Matching und den Virtual Threads Teile finalisiert wurden, bleiben andere Aspekte weiterhin im Preview- oder auch Inkubator-Modus. Für Entwickler besonders spannend sind aber einige ganz neue Themen wie die String Templates, die "Unnamed Classes and Instance Main Methods" (beide zunächst als Preview dabei) und die Sequenced Collections
Beim Pattern Matching geht es darum, bestehende Strukturen mit Mustern abzugleichen, um komplizierte Fallunterscheidungen effizient und wartbar implementieren zu können. Es wird bereits seit einigen Jahren im Rahmen von Project Amber entwickelt. Bis zum letzten Release mit LTS-Support (OpenJDK 17) wurden zunächst nur Basis-Funktionen wie Switch Expressions, Sealed Classes, Records und Pattern Matching for instanceof abgeschlossen. Mit Java 21 kann es nun auch produktiv eingesetzt werden. Mit den Record Patterns und dem Pattern Matching for switch wurden zwei wichtige Bausteine finalisiert. Bei den Record Patterns handelt es sich um einen neuen Pattern Typ, der Records matched und gleichzeitig in ihre Bestandteile zerlegt (dekonstruiert), sodass direkt mit den Komponenten weitergearbeitet werden kann. Und mit Pattern Matching for switch werden alle bisherigen Funktionen zusammengebracht, sodass man in switch-Expressions nun neben den schon immer verfügbaren Constant Patterns auch Type- und Record Patterns inklusive der when-Clauses (früher Guarded Patterns genannt) einsetzen kann. Ganz frisch (in 21 als Preview eingeführt) sind die Unnamed Patterns. Dadurch lassen sich Platzhalter (der Unterstrich _) verwenden, wenn die Pattern Variablen nicht ausgewertet werden sollen. Das macht den Code kompakter, besser lesbar und weniger fehleranfällig (vermeidet toten Code). Dieses Konstrukt lässt sich auch in catch-Blöcken oder bei Lambda-Parametern als sogenannte Unnamed Variables einsetzen.
Da sind sich viele Experten einig und zeigen ihre Begeisterung für eines der wichtigsten Features in der Geschichte von Java, das sich direkt in eine Reihe mit Generics, Lambda-Ausdrücken und dem Plattform-Modul-System einreiht. Die Virtual Threads erlauben eine viel größere Anzahl gleichzeitiger Threads. Sie haben besonders beim Ressourcenverbrauch die Nase vorn gegenüber den klassischen Plattform-Threads. Dadurch können viel mehr (theoretisch Millionen) der virtuellen Threads in einem Prozess gestartet werden, ohne gleich an die Speichergrenzen der VM zu stoßen. Das ermöglicht eine bessere Auslastung der CPU. Die meisten Java Entwickler werden allerdings nicht direkt mit nebenläufiger Programmierung und Virtual Threads in Berührung kommen. Sie profitieren trotzdem, weil Framework-Hersteller (Spring, Quarkus, ...) Virtual Threads unter der Haube einbauen werden. Das ermöglicht gerade bei Webanwendungen eine bessere Auslastung, weil mehr eingehende Requests von Nutzern gleichzeitig verarbeitet werden können. Die Bremse sind ohnehin IO-Zugriffe (z. B. auf die Datenbank). Da die Requests aber sehr günstig zu erzeugen und zu betreiben sind (geringerer Speicherverbrauch) und so viel mehr gleichzeitig verwaltet werden können, lassen sich die vorhandenen Ressourcen besser nutzen.
Im Umfeld der nun abgeschlossenen Virtual Threads gibt es noch zwei, erneut als Preview erschienene Funktionen, die Structured Concurrency und die Scoped Values. Letzteres ist eine bessere Alternative zu den ThreadLocal-Variablen. Und Structured Concurrency ermöglicht die Bearbeitung von mehreren parallelen Teilaufgaben auf eine besonders les- und wartbare Art und Weise.
Einige der vom Funktionsumfang überschaubaren Features sind etwas überraschend im Release von Java 21 aufgetaucht. Besonders spannend sind die String Templates. Sie bringen nicht nur die lang ersehnte String Interpolation in die Java Welt. Die Implementierung ermöglicht in Zukunft sogar das einfache Erstellen eigener Template Prozessoren, die aus Texten mit Platzhaltern zum Beispiel JSON Objekte oder sichere Datenbankabfragen mit PreparedStatements erzeugen können.
Sequenced Collections bringen eine Handvoll neuer Methoden für Collections, deren Elemente in einer wohldefinierten Reihenfolge geordnet sind. Dazu zählen Lese- und Schreibzugriffe (inklusive Entfernen) auf das erste beziehungsweise letzte Element und das Umdrehen der Reihenfolge (reversed()). Dadurch wird das immer noch viel benutzte Collection Framework weiter aufgewertet.
Für Java-Einsteiger wird es in Zukunft auch leichter. Sie müssen zum Erstellen einer ausführbaren Klasse nicht mehr gleich die speziellen Konstrukte wie class, static, public, String[] args usw. verstehen. Mit dem "JEP 445: Unnamed Classes and Instance Main Methods (Preview)" kann man jetzt ausführbare main-Methoden kürzer und prägnanter definieren und sogar auf eine umschließende Klasse verzichten. In Kombination mit dem im JDK 11 veröffentlichten JEP "Launch Single-File Source-Code Programs" lassen sich sehr schlank kleine Java-Anwendungen in einer Textdatei mit einer simplen void main(){}-Methode von der Kommandozeile aufrufen. Und das kommt dann auch erfahrenen Java-Entwicklern zugute.
Die Vector API ist ein Dauerläufer und taucht seit Java 16 regelmäßig in den Releases auf, diesmal als sechster Inkubator (Vorstufe von Preview). Es geht dabei um die Unterstützung der modernen Möglichkeiten von SIMD-Rechnerarchitekturen mit Vektorprozessoren. Single Instruction Multiple Data (SIMD) lässt mehrere Prozessoren gleichzeitig unterschiedliche Daten verarbeiten. Durch die Parallelisierung auf Hardware-Ebene verringert sich beim SIMD-Prinzip der Aufwand für rechenintensive Schleifen.
Auch schon seit einigen Versionen mit an Bord ist die Foreign Function & Memory API, diesmal im 3. Preview. Wer schon sehr lange in der Java-Welt unterwegs ist, wird das Java Native Interface (JNI) kennen. Damit kann nativer C-Code aus Java heraus aufgerufen werden. Der Ansatz ist aber relativ aufwändig und fragil. Die Foreign Function API bietet einen statisch typisierten, rein Java-basierten Zugriff auf nativen Code (C-Bibliotheken). Zusammen mit dem Foreign-Memory Access API kann diese Schnittstelle den bisher fehleranfälligen und langsamen Prozess der Anbindung einer nativen Bibliothek beträchtlich vereinfachen. Mit letzterer bekommen Java-Anwendungen die Möglichkeit, außerhalb des Heaps zusätzlichen Speicher zu allokieren. Ziel der neuen APIs ist es, den Implementierungsaufwand um 90 Prozent zu reduzieren und die Leistung um Faktor 4 bis 5 zu beschleunigen. Beide APIs sind seit dem JDK 14 beziehungsweise 16 im JDK zunächst einzeln und ab 18 als gemeinsamer Inkubator-JEP enthalten. Vermutlich wird sich dieses Feature langsam der Finalisierung nähern.
Bei den Garbage Collectors hat sich auch etwas getan. Der vor wenigen Jahren im OpenJDK 15 eingeführte ZGC (Scalable Low-Latency Garbage Collector) gehört zu einer neuen Generation. Das Ziel ist, große Datenmengen (TB von RAM) mit möglichst kurzen GC-Pausen (kleiner 10 ms) aufzuräumen und so die Anwendung nahezu immer antwortbereit zu halten. Bisher machte der ZGC keine Unterscheidung zwischen frischen und schon länger existierenden Objekten. Die Idee ist, dass Objekte, die bereits einen oder mehrere GC-Läufe überlebt haben, auch in Zukunft wahrscheinlich noch lange weiterleben werden. Diese werden dann in einen extra Bereich (Old Generation) verschoben und müssen so bei den Standard-Läufen nicht mehr verarbeitet werden. Das kann einen Schub bei der Performance einer Anwendung geben.
Es gibt noch einige weitere, für Entwickler nicht so relevante JEPs. Für die vielen Details lohnt auch ein Blick auf die Release Notes [3]. Änderungen am JDK (Java Klassenbibliothek) lassen sich zudem sehr schön über den Java Almanac [4] nachvollziehen. In dieser Übersicht finden sich unter anderem alle Neuerungen zu den String Templates und den Sequenced Collections. In der String-Klasse gab es auch kleine Erweiterungen: Beispielsweise wurde die Methode indexOf(String str, int beginIndex, int endIndex) eingeführt, die in einem bestimmten Bereich nach einem Teil-String sucht. Die Klassen StringBuffer und StringBuilder wurden jeweils um zwei ähnliche Methoden erweitert, die ein Zeichen oder eine Zeichenkette wiederholt mal an das bestehende Objekt anhängen: repeat(CharSequence, int). Die Klasse Character wurde wiederum um eine Vielzahl von Methoden erweitert, die prüfen, ob eine Unicode-Zeichen ein Emoji oder eine Variante davon darstellt. Java geht also auch hier mit der Zeit.
Einen ausführlichen Überblick über das Java-21-Release findet sich im frisch erschienen iX-Artikel [5]. Durch die Finalisierung der Virtual Threads wird Java 21 in ein paar Jahren auf eine Stufe mit Java 5 (Generics), Java 8 (Lambdas, Stream-API) und Java 9 (Plattform Modul System) gestellt werden. Auch wenn sich das Potenzial dieser Idee noch nicht vollständig erfassen lässt, werden die virtuellen Threads die Implementierung von hochskalierbaren Server-Anwendungen in Zukunft stark vereinfachen. Aber Java 21 hält noch viele andere Highlights bereit. Beim Pattern Matching wurden das Kernstück (Pattern Matching for switch) sowie die Record Patterns finalisiert und die Unnamed Patterns als Preview eingeführt. Die String Templates (im Moment noch als Preview) und auch die Sequenced Collections erleichtern Java-Entwicklern das Leben. Das steigert schon jetzt die Vorfreude auf das nächste Release im März 2024.
URL dieses Artikels:
https://www.heise.de/-9309203
Links in diesem Artikel:
[1] https://java.bettercode.eu/
[2] https://java.bettercode.eu/index.php#programm
[3] https://jdk.java.net/21/release-notes
[4] https://javaalmanac.io/jdk/21/apidiff/17/
[5] https://www.heise.de/tests/Java-21-Version-mit-LTS-String-Templates-und-Virtual-Threads-vorgestellt-9302335.html
[6] mailto:rme@ix.de
Copyright © 2023 Heise Medien
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