=====TensorFlow & Kommunikation mit dem Arduino per Bluetooth===== ====1. Einführung==== [[https://de.wikipedia.org/wiki/TensorFlow|TensorFlow]] ist ein Framework, welches eine vereinfachte Anwendung von Algorithmen aus dem maschinellen Lernen ermöglicht. Damit kann man z.B.: * [[https://github.com/tensorflow/tfjs-models/tree/master/posenet|Posenet]]: die Stellung von einem oder mehreren Menschen erkennen, * [[https://github.com/tensorflow/tfjs-models/tree/master/body-pix|Body-Pix]]: den menschlichen Körper segmentieren, * [[https://github.com/tensorflow/tfjs-models/tree/master/facemesh|Facemesh]]: markante Punkte im Gesicht erkennen, * [[https://github.com/tensorflow/tfjs-models/tree/master/handpose|Handpose]]: die Handstellung erkennen usw. Es gibt TensowFlow-Implementationen für verschiedene Sprachen, z.B. Python, C++, Java und JavaScript. Die JavaScript-Variante scheint einfacher für den Start zu sein. Euer TensorFlow-Code läuft dabei im Browser. Probiert die TensorFlow-Beispiele aus, indem ihr auf die entsprechenden Links in der Liste oben klickt! TensorFlow ist rechenintensiv. Deswegen ist es (zu diesem Zeitpunkt im Januar 2021) ziemlich langwierig, es auf einem Smartphone oder Raspberry Pi zu betreiben (bzw. wir haben noch keine gute Lösung gefunden). Es gibt zwar die abgespeckte Variante [[https://www.tensorflow.org/lite/api_docs|TensorFlow Lite]], jedoch scheint es momentan einfacher, TensorFlow auf einem PC zu laufen und nur die Ergebnisse bei Bedarf über serielle Kommunikation an den Arduino zu schicken. Bei mobilen Robotern kann die serielle Kommunikation über Bluetooth laufen. Diesem Thema (TensorFlow & Kommunikation über die serielle Schnittstelle) widmet sich dieser Artikel. Bei mobilen Robotern kann man über die App [[https://www.dev47apps.com/|DroidCam]] die Handy-Kamera als externe kabellose Webcam nutzen. Wenn der Rechner und das Handy beide im gleichen WLAN-Netzwerk sind, kann man sie kabellos über WLAN verbinden. Die dafür erforderliche IP-Adresse wird in der App angezeigt. ====2. Einfacher(er) Einstieg in TensorFlow==== Da ihr euch im Crashkurs mit Processing vertraut gemacht habt, könnt ihr [[https://p5js.org/|p5.js]] nutzen -- die Processing-Variante für JavaScript. [[https://www.youtube.com/watch?v=EA3-k9mnLHs|Hier]] gibt es eine gute Einleitung, wie man die TensorFlow-Bibliothek Posenet (für die Einschätzung der menschlichen Stellung) in p5.js verwendet. In dieser Video-Einleitung wird das Programm im [[https://editor.p5js.org/|p5.js-editor]] ausgeführt. Alternativ könnt ihr Dasselbe in Textdateien auf eurem Rechner abspeichern und ausführen. Dass es verschiedene Text-Dateien gibt, wird auch im Einleitungsvideo (ab ca. 26. Min.) erwähnt. Die .html-Datei beinhaltet das Grundgerüst der Webseite, in der .js-Datei läuft euer Code. Informiert euch, warum JavaScript überhaupt in Webseiten verwendet wird und wozu der \\ In Zeile 5 bezieht sich der html-Code auf die JavaScript-Datei script.js. Diese Datei müsst ihr selbst erstellen, in den **selben Ordner** wie index.html legen und dorthin den Code aus dem p5.js-editor kopieren. (Wenn ihr dieser Datei einen anderen Namen wie script.js vergeben habt, müsst ihr die entsprechende Stelle in der html-Datei ändern.) Wenn ihr nun index.html in eurem Browser öffnet, soll dort die selbe Anwendung wie auf der p5.js-editor-Zeichenfläche laufen. Testet das, bevor ihr zum nächsten Punkt dieses Artikels übergeht. ====3. Lokalen Server einrichten==== ==3.1. Node.js-Installation== [[techniken::tensorflownodebluetooth::windows-lokal#Node.js auf Windows: Posenet mit lokal gehosteter p5.js Instanz]] Wir konnten schon über den p5.js-editor die Ergebnisse von einem TensorFlow-Modell bekommen, warum brauchen wir den Server? Grund ist, wir wollen auf den //seriellen Port// zugreifen, um Daten an den Arduino zu schicken, und das geht nicht von der Client-Seite (Browser). In unserem Fall wird der Server auf dem gleichen Rechner laufen wie der Client (Browser), deswegen sagen wir, dass wir einen //lokalen// Server einrichten. Eine Internetverbindung brauchen wir trotzdem, da unsere Datei ''index.html'' Links zu den Bibliotheken //p5.js// und //ml5.js// beinhaltet. Informiert euch darüber, was ein Server und ein Client sind. Es gibt viele Möglichkeiten, einen Server zu programmieren, z.B. mit Java, PHP oder Python. Wir werden einen [[https://de.wikipedia.org/wiki/Node.js|Node.js]]-Server einrichten, u.A. weil Node.js ebenso auf JavaScript geschrieben ist. Node.js verfügt über den Paket-Manager ''npm'', über welchen man zusätzliche Node-Module installiert. [[https://nodejs.org/en/download/|Ladet Node.js und npm herunter]] und installiert sie. Anschließend könnt ihr mit den Befehlen ''node %%--%%v'' und ''npm %%--%%v'' auf dem Terminal (bzw. in der Anwendung **cmd**) überprüfen, ob alles geklappt hat. (Die Node/npm-Version wird angezeigt => geklappt.) ==3.2. Hallo Welt== Erstellt einen Ordner (ich nenne ihn ''NodeOrdner'' -- andere Namen möglich), wo ihr Dateien für euren Server ablegen werdet. Es hilft, wenn dieser Ordner auf dem Laufwerk ist, wo ihr Anwendungen ausführen dürft. Wechselt über den ''cd''-Terminal-Befehl zu diesem Ordner. (Bei Bedarf sucht online, wie man mit ''cd'' zwischen Ordnern wechseln kann.) Wenn ihr im richtigen Ordner seid, führt diesen Befehl aus -- er initialisiert euer Projekt:\\ npm init Bestätigt mit der Enter-Taste (mehrfach) die Standard-Einstellungen. Die Datei ''package.json'' wird automatisch angelegt, wo diese Einstellungen gespeichert sind. Installiert auch die Node-Module, die wir später brauchen werden: * //express// (minimalistische Node-Variante): ''npm install express %%--%%save'' * //body-parser// (analysiert HTTP-Requests): ''npm install body-parser %%--%%save'' * //serialport// (ermöglicht den Zugriff auf serielle Ports): ''npm install serialport %%--%%save'' Erstellt im Ordner die Datei ''server.js'' (andere Namen möglich) mit dem folgenden Inhalt: var express = require('express'); // den Node-Modul express einbinden const hostname = '127.0.0.1'; // localhost const port = 3000; // Portnummer, kann geaendert werden const folderName = 'public'; // nutze Dateien aus diesem Ordner var app = express(); app.use(express.static(folderName)); // nutze Dateien aus diesem Ordner var server = app.listen(port, function () { // starte den Server console.log("Example app listening at http://%s:%s", hostname, port) // console.log() ist wie println() }) \\ In diesem Beispiel starten wir den Server auf der lokalen Maschine (127.0.0.1, oder man kann stattdessen auch //localhost// schreiben) auf dem Port 3000 (andere Portnummer möglich). Dieser Server wird Dateien aus einem konkreten Ordner bereitstellen. Im Code haben wir diesen Ordner ''public'' genannt (andere Namen möglich). Diesen Ordner gibt es noch nicht. Deswegen müssen wir im Ordner ''NodeOrdner'' einen Unterordner ''public'' erstellen. In diesem Unterordner erstellt bitte eine Datei ''index.html'' (andere Namen möglich), öffnet diesen und speichert dort eine Zeile: //Hallo Welt//. Jetzt startet den Server. Im Terminal im Ordner ''NodeOrdner'' führt dafür den folgenden Befehl aus: node server.js Geht im Browser auf die Seite ''http://localhost:3000/index.html'' (oder ''http://127.0.0.1:3000/index.html'', was das Selbe ist). Wenn ihr "Hallo Welt" seht, funktioniert alles richtig. Was haben wir gerade gemacht? Wir haben einen Server eingerichtet, der in der Lage ist, Dateien (bzw. Inhalte dieser Dateien) aus dem bestimmten Ordner (in unserem Fall ''public'') bereitzustellen. Dann haben wir einen Browser gestartet, der die konkrete Datei ''index.html'' angefordert hat. Diese Datei befand sich in dem Ornder, und so wurde ihr Inhalt an den Browser bereitgestellt: "Hallo Welt". Das war einfacher Text, damit kann der Browser nicht viel mehr anfangen. Wenn die Datei stattdessen html-Code beinhaltet hätte, könnte der Browser diesen entsprechend verarbeiten -- das machen wir im nächsten Schritt. ==3.3. Eure Dateien über den Server bereitstellen== Löscht die Datei ''index.html'' aus dem Ordner ''public'' und fügt die im Punkt 2 erstellten Dateien ''index.html'' und ''script.js'' hinzu. Ruft im Browser den Link ''http://localhost:3000/index.html'' auf. Wenn alles richtig ist, seht ihr euer Programm mit TensorFlow/Posenet. ====4. GET & POST Requests==== Der Server und der Client (Browser) kommunizieren über das //Hypertext Transfer Protocol//, HTTP ([[https://www.ionos.de/digitalguide/hosting/hosting-technik/was-ist-http/|kurze Erklärung]]). Der Client kann an den Server bestimmte Anfragen schicken, am häufigsten kommen der GET- und der POST-Request vor ([[https://www.ionos.de/digitalguide/hosting/hosting-technik/http-request-erklaert/|kurze Erklärung]]). In unserem Beispiel könnten wir sowohl GET als auch POST verwenden; Im folgenden Beispiel wird ein POST-Request verschickt. Der POST-Request kann im Code so aussehen (es kann auch was anderes statt "distance" und "dist" stehen, aber es muss der gleiche String an beiden Seiten sein): var distance = 42; // der Wert, den man an den Server schickt var xhttp = new XMLHttpRequest(); xhttp.open("POST", '/dist', true); //Den richtigen POST-Request-Header schicken: xhttp.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); xhttp.onreadystatechange = function() { // Diese Funktion wird aufgerufen, sobald der Zustand sich aendert if (this.readyState === XMLHttpRequest.DONE && this.status === 200) { // Request ausgefuehrt. Weitere Aktionen koennen ausgefuehrt werden. } } xhttp.send("distance=" + distance); // den Wert als Eigenschaft distance schicken const bodyParser = require("body-parser"); // Node-Modul body-parser einbinden app.use(bodyParser.urlencoded({extended : true})); app.post('/dist', (req, res) => { console.log('Got distance:', req.body.distance); res.sendStatus(200); // TODO: weitere Aktionen }); \\ ====5. Seriellen Bluetooth-Port einrichten==== Überpfüft, dass auf eurem Rechner ein Bluetooth-Port verfügbar ist. Auf einem Linux-Rechner kann man das z.B. so überprüfen (das Sternchen * steht für eine beliebige Nummer/Index des Ports, der Befehl wird im Terminal ausgeführt): ls /dev/rfcomm* Falls Bluetooth-Ports verfügbar sind, werden ihre Namen angezeigt, z.B. ''/dev/rfcomm0'', ''/dev/rfcomm1'' usw. Wenn "No such file or directory" zurück kommt, sind keine Bluetooth-Ports verfügbar, und wir müssen den Port extra einrichten (oder euer Rechner ist nicht Bluetooth-fähig). Um das zu tun, müssen wir schauen, welche Bluetooth-Geräte überhaupt empfangen werden, das machen wir mit dem folgenden Befehl: sudo bluetoothctl Es werden Adressen (z.B. "F4:5E:AB:AA:BB:CC") und Namen (z.B. "HC-05") von den verfügbaren Geräten angezeigt. Notiert euch die Adresse vom Gerät, mit welchem ihr die serielle Kommunikation aufbauen wollt. Anschließend erstellen wir den Port mit dem Namen z.B. ''/dev/rfcomm0'', über welchen wir uns mit diesem Gerät verbinden werden: sudo rfcomm bind 0 F4:5E:AB:AA:BB:CC 1 Überpfüft mit ''ls /dev/rfcomm*'', ob das geklappt hat. Den Portnamen ''/dev/rfcomm0'' werden wir im nächsten Teil im Node.js-Code benutzen. Zum Testen ist es manchmal hilfreich, wenn man direkt im Terminal die serielle Kommunikation betreiben kann. Dafür könnt ihr z.B. das Programm //picocom// nutzen (Installation: ''sudo apt-get install picocom''): picocom /dev/rfcomm0 \\ ====6. Daten über den seriellen Port vom Server zum HC-05 schicken==== Wenn wir den seriellen Port erstellt haben und seinen Namen kennen, können wir Daten vom Server verschicken (dieser Code gehört in die ''server.js''-Datei): const SerialPort = require('serialport'); // Node-Modul serialport einbinden const arduinoSerialPort = new SerialPort('/dev/rfcomm0', { baudRate: 9600 }); // Den seriellen Portnamen und Baudrate festlegen arduinoSerialPort.write("Hello..."); // Daten schicken (Strings oder Zahlen) \\ In diesem Beispiel wird der Port ''/dev/rfcomm0'' verwendet -- der Bluetooth-Port auf einem Linux-PC. Wenn ihr die serielle Kommunikation über einen USB-Kabel betreibt, nennt sich euer Port evtl. ''/dev/ttyUSB0'', ''COM3'' oder Ähnliches. Das sind die gleichen Namen, die in der Arduino IDE unter Werkzeuge=>Port zu finden sind.