Dateikomprimierung auf dem Mac

Die Datei überschreitet die zulässige Dateigröße.
Wem dieser Satz nur allzu vertraut ist, der wird sicherlich den ein oder anderen Abend mit der Suche nach Komprimierungsmöglichkeiten verbracht haben. Die Scans der Heimdrucker werden immer hochauflösender, Bilder mit immer besseren Kameras aufgenommen und bei Videos etabliert sich so langsam 4k als Standard. Doch so manche Internetleitung oder Website macht bei so großen Dateien nicht mit.
Im Folgenden möchte ich deshalb einige nützliche Programme auf dem Mac vorstellen, die das digitale Leben ein wenig leichter machen.

PDF-Dokumente

Am häufigsten dürfte wohl dieser Fall eintreten: Für eine Bewerbung oder einen Antrag muss ein Dokument hochgeladen werden, leider umfasst dieses mehrere Seiten, die natürlich in guter Qualität vorliegen – man muss sie ja schließlich gut lesen können.
Das ausgewählte Dokument überschreitet die zulässige Dateigröße von (hier lächerlich kleine Zahl einfügen). Bitte versuchen Sie es erneut.“
Und jetzt?
Das Programm Vorschau, das auf jedem Mac installiert ist, liefert bereits einen eigenen Filter für das Reduzieren der Dateigröße. Man muss einfach nur das entsprechende Dokument öffnen und es anschließend exportieren. Dabei hat man die Möglichkeit einen „Quartz-Filter“ hinzuzufügen. Hier einfach „Reduce File Size“ auswählen und bestätigen. Wem dieser vorgefertigte Filter zu stark ist, der kann über das Programm „ColorSync“ (ebenfalls auf jedem Mac vorhanden) unter „Filter“ auch eigene Einstellungen vornehmen. Dabei legt man am besten einen neuen Filter (beispielsweise „Reduze File Size Good“) an und geht wie folgt vor: Rechts neben dem Namen des Filters ist ein kleiner Pfeil, dort fügt man „Bild-Anpassung (Farbe)“ und „Bild-Komprimierung“ hinzu. Bei „Bild-Anpassung“ sollte man die Qualität auf „Hoch“ und den Haken bei „Auflösung festlegen“ setzen und als Wert 100 eingeben. Bei „Bild-Komprimierung“ sollte der Modus auf JPEG gesetzt und der Schieberegler etwa im rechten Drittel platziert sein. Ausgehend von diesen Werten kann man dann je nach geforderter Größe und der gewünschten Qualität etwas experimentieren.

Fotos

Bei Fotos reicht es meist schon, das Dateiformat von dem Apple-üblichen .png auf .jpg zu ändern. Dies geht ebenfalls ganz einfach über die Vorschau. Man exportiert das Bild und wählt als Dateiformat .jpg. Dort kann man auch gleich mittels eines Schiebereglers die Qualität festlegen. Aber Achtung: stellt man die Qualität zu niedrig ein, kann es schnell zu sogenannten Artefakten, also Störungen und Verpixelungen im Bild kommen. Da die Dateigröße von JPEGs ohnehin geringer ist, kann man den Regler ruhig auf maximale Qualität stellen.

Videos

Hier scheitern leider die Bordmittel von Apple, daher stelle ich ein kleines Tool für die möglichst verlustfreie Komprimierung von Videodateien vor: Handbrake! Das Programm ist Open Source und somit kostenlos nutzbar. Beim Starten wählt man aus, welche Videodatei man bearbeiten möchte und im Anschluss bekommt man vielerlei Einstellungsmöglichkeiten um die Ohren gehauen. Unter dem Menüpunkt „Übersicht“ lässt sich praktischerweise das Dateiformat einstellen, was das Programm auch zur Konvertierung wirklich nützlich macht. Der für die Komprimierung wichtige Teil befindet sich jedoch unter dem Punkt „Video“. Hier kann eine Reduzierung der Bildfrequenz oder ein direktes Einstellen der Qualität zu geringeren Dateigrößen genutzt werden. Man sollte natürlich möglichst sparsam einstellen, denn gerade bei Videos macht sich eine schlechte Qualität schnell negativ bemerkbar. Es gilt: Experimentieren und immer wieder exportieren, um die Dateigröße zu überprüfen.

Damit sollte die nächste Uploadgrenze einer Website kein Hindernis mehr sein!

Auch Apple kann kompliziert: Mails signieren und verschlüsseln mit iPhone und iPad

Auf die Gefahr hin, dass die letzten Beiträge unseres Blogs ein klein wenig Apple-lastig werden, folgt auf den letzten Beitrag ein weiterer über iGeräte, genauer: Über verschlüsselte E-Mails, und warum die Sache mit der Usability nicht immer so einfach ist, wie man das als User*in gerne hätte.

Haben Sie, liebe Blog-Lesende, schon einmal eine E-Mail verschlüsselt? Nein? Dann gehören Sie wahrscheinlich zur großen Mehrheit von IT-Nutzenden, die das Thema zwar „schon einmal irgendwo gehört oder gelesen“ haben, aber sich selbst noch nie damit befasst haben.

Zumindest könnten Sie einmal darüber nachdenken, Ihre E-Mails mit Hilfe eines S/MIME-Zertifikats zu signieren, was gleichzusetzen ist mit einer Unterschrift. Wie Sie an der Uni Köln ein solches Zertifikat beantragen und wofür das gut ist, können Sie hier nachlesen.

Wie man sein Zertifikat dann auf verschiedenen Geräten einrichtet und nutzt, haben wir an dieser Stelle dokumentiert. Einmal eingerichtet, ist das Ganze sofort lauffähig, und Ihre Mails werden fast unmerklich digital signiert. In diesem Beitrag legen wir den Fokus auf mobile Apple-Geräte. iOS unterstützt S/MIME-Zertifikate und die E-Mail-Verschlüsselung schon seit dem Jahr 2015 – so neu ist das Thema zugegebenermaßen also nicht.

Bildschirmabbild, das das Fenster zum Erstellen einer neuen E-Mail zeigt

Verschlüsselte oder signierte E-Mails zu empfangen ist kinderleicht. Neben dem Absende-Namen taucht bei einer signierten und/oder verschlüsselten E-Mail ein kleines Häkchen und gegebenenfalls ein Schlosssymbol auf.

Jetzt kommt der Teil, der komplizierter ist, als es sein müsste: Spannend wird es, wenn man seine Mails neben dem Signieren auch verschlüsseln möchte. Dazu schweigt sich interessanterweise sogar die Anleitung von Apple selbst aus. Das führt dann dazu, dass man zunächst einmal eine Fehlermeldung erhält, wenn man es mit dem Verschlüsseln versucht (siehe Bild). Wo hakt’s? Die Anleitung von Apple sagt, dass in diesem Fall das Zertifikat der Empfänger*in nicht gefunden wurde. Aber kein Wort darüber, was man nun tun soll, damit das iGerät diesen öffentlichen Schlüssel der anderen Person kennenlernen kann…?

Spoiler: Dazu muss man das Zertifikat tatsächlich „installieren“, sagt die Oberfläche, wenn man ein bisschen danach sucht.

Hat man diese Schritte einmal gefunden, ist es ganz einfach:

1.) Eine E-Mail der Person öffnen,
2.) in der Kopfzeile auf den Namen klicken,
3.) gegebenenfalls nochmals auf den Namen klicken, dann
4.) auf „Zertifikat anzeigen“, und zum Schluss
5.) auf „Installieren“ klicken.


Bildschirmabbild, das die Details eines Mail-Zertifikats anzeigt

Diese Schritte muss man für jede Person vornehmen, der man verschlüsselte E-Mails senden will.

Zusammenfassend kann man sagen: Wenn man einmal weiß, was man tun muss, ist es gar nicht so kompliziert. Aber warum gestaltet man – beziehungsweise Apple – diesen Vorgang so umständlich? Einfacher wäre es wie beim Open-Source-Mail-Programm Mozilla Thunderbird: Hier wird der öffentliche Schlüssel der Absender*in – also der Teil des Zertifikats, der dem iOS-System wie oben genannt zunächst „bekannt gemacht“ werden muss – direkt automatisch gespeichert.

Kurzes Fazit: Weniger ist mehr. Ich würde mir wünschen, dass Apple dieses Prozedere vielleicht etwas einfacher gestaltet und zukünftig weniger Arbeitsschritte nötig sind. Das würde zur Akzeptanz von E-Mail-Zertifikaten und deren Einsatz sicher enorm beitragen.

Wie die iOS-App „Kurzbefehle“ den Alltag erleichtern kann

Die Anwendung, die ich heute vorstellen möchte, ist ein wirklich mächtiger Helfer, der an das Mac Programm „Automator“ angelehnt ist. Mit diesem lassen sich lästige Aufgaben zu Routinen zusammenfassen und mit einem einzigen Klick ausführen. Eine abgewandelte Form gibt es seit iOS 12 auch auf iPhone und iPad.
Die App gehört nicht zu den standardmäßig vorinstallierten Apps und muss erst aus dem App Store geladen werden. Aber dafür ist sie Apple-typisch natürlich kostenlos. Öffnet man die App zum ersten Mal ist die Oberfläche ein wenig gewöhnungsbedürftig und es wird schnell klar, dass sich die Anwendung eher an Erfahrene richtet. Möglicherweise ist es hilfreich erst einmal in der Galerie zu stöbern und ein paar der Tools dort unter die Lupe zu nehmen. Der Trinkgeldrechner zum Beispiel ist sehr praktisch oder auch der Wäschetimer, der eine Erinnerung sendet, wenn die Waschmaschine fertig ist. Das erspart zum einen das Warten in der ungemütlichen Waschküche, bis nach 10 Minuten endlich die letzte Minute des Waschgangs abgeschlossen ist und zum anderen vergisst man die Wäsche nicht, wenn man unterwegs ist.
Wenn man sich einmal in die App reingedacht hat, ist das Erstellen eigener Routinen aber gar nicht so schwer. Als Beispiel habe ich eine Morgenroutine erstellt, weil mir aufgefallen ist, dass ich jeden Morgen die gleichen Apps brauche. Über das Plus in der Bibliothek lege ich also nun eine neue Routine an. Vom Grundprinzip her lege ich in der Mitte alle Aktionen ab, die ablaufen sollen. Das iPhone spielt diese dann eine nach der anderen ab. Eigentlich ein simples Prinzip.
Zunächst möchte ich also, dass mein iPhone morgens das WLAN einschaltet. Über die Suche gebe ich deshalb einfach „WLAN“ ein und es erscheint die Aktion „WLAN konfigurieren“ – genau das will ich. Es erscheint die Aktion, daneben befindet sich ein Kippschalter, der auf grün steht. Das WLAN wird also eingeschaltet. Als nächstes möchte ich das Wetter angezeigt bekommen. Ich tippe also in die Suche „Wetter“ ein und schon erscheint die Aktion „Wetter am aktuellen Standort anzeigen“. Perfekt.

Und nun meine E-Mails! Damit ich genug Zeit habe mir zu überlegen, was ich heute anziehe, bevor sich die E-Mail-App öffnet, füge ich die Aktion „Warten“ hinzu. Fünf Sekunden sollten genügen, im Zweifelsfalle lässt sich die Routine nachher jederzeit anpassen und die Zeit hoch oder runter setzen. Nun also zu den E-Mails. Dazu müssen wir einen kleinen Umweg gehen, und zwar über die Aktion „App öffnen“. Nachdem die Aktion unter den anderen erscheint, wählt man dann die E-Mail App seiner Wahl aus. Tada!Übrigens, wen es auch ärgert, dass sich WLAN und Bluetooth über das Kontrollzentrum nicht mehr vollständig abschalten lassen, der sollte sich dafür eine Routine zusammenstellen. Die Aktionen „WLAN konfigurieren“ und „Bluetooth konfigurieren“ schalten WLAN und Bluetooth nämlich mit einem Klick komplett aus.
Alle Helfer lassen sich entweder im Widget anzeigen oder als App auf dem Home Bildschirm ablegen. Mehr Konfigurationsmöglichkeiten findet man in der erstellten Routine oben rechts, wenn man auf das Symbol der beiden Kippschalter tippt. Mit einem eigenen Hotword lassen sich die Routinen sogar zu Siri hinzufügen.

 

Sprachassistent im Eigenbau – Snips auf dem Raspberry Pi

„Alexa, wie wird das Wetter heute in Köln?“
Sprachassistenten werden immer beliebter. Ob zu Hause auf dem Smart-Speaker oder mobil auf iPhone und Co. Denkt man am Anfang noch, dass man so eine Spielerei niemals brauchen wird, merkt man schnell, dass die smarten Helfer einem doch so manchen Arbeitsschritt ersparen können. Ein Timer für die Pizza ist mit einem kurzen Sprachbefehl eben schneller gestellt, als erst einmal das Smartphone entsperren zu müssen und dann die richtige App zu finden. Der Nachteil: Die aufmerksamen Helfer hören ständig mit. Was also tun, wenn man seine Daten nicht irgendwo auf einem Server in den USA wissen will?

Für Bastler gibt es mit dem Alleskönner Raspberry Pi eine eingeschränkte, aber passable Lösung. Snips heißt der Sprachassistent, der ohne Cloud auskommt und überdies auch nicht ständig eine Internetverbindung benötigt – alle Daten bleiben lokal auf dem Rechner.
Für das Projekt habe ich mir kurzerhand meinen vorhandenen Raspberry Pi 3, auf dem mein MagicMirror läuft, ein USB-Mikrofon und einen günstigen Lautsprecher mit Aux-Kabel geschnappt und losgelegt.

Die Dokumentation von Snips ist wirklich ausführlich, und sofern man ein bisschen Erfahrung mit der Eingabe von Terminalbefehlen hat, auch gut nachvollziehbar. Aber auch Personen ohne Vorkenntnisse sollten mit der Anleitung zurechtkommen. Mein Vorteil war es, dass mein Raspi bereits eingerichtet war und über eine WLAN-Verbindung, aktivierten SSH-Zugriff sowie Node verfügte. Das hat die Zeit der Vorbereitung deutlich verringert. Mein Perfektionismus hat das Ganze dann allerdings wieder um einige Stunden gestreckt – doch zu meinen Versuchen den Assistenten umzubenennen später mehr. Zunächst einmal zur eigentlichen Konfiguration des Sprachassistenten: Die „Skills“, wie sie beispielsweise bei Alexa genannt werden, können über eine übersichtliche Online-Plattform eingespielt werden. Sobald „Sam“, das Kommandozeilen-Interface installiert ist, kann man die Skills jederzeit aktualisieren. Die Auswahl ist noch überschaubar: Wetter über openweather anzeigen, Witze erzählen, Rechnen, einen Timer stellen aber auch Smarthome-Geräte steuern. Auch eigene Befehle lassen sich programmieren. Dafür sollte man aber Zeit und ein bisschen Know-How mitbringen.
„Hey Snips, wie wird das Wetter heute in Köln?“ funktionierte ebenso gut wie „Hey Snips, erzähle einen Witz.“ Spoiler: Die Witze stehen Alexas Flachwitzen in nichts nach. Sie sind nur ein weniger schlechter zu verstehen, weil Snips‘ Sprachausgabe etwas von einem Dalek hat.

Kommen wir aber zurück zum Perfektionismus: Schon als Siri auf dem iPhone Einzug hielt, entbrannte vermutlich nicht nur in mir der Wunsch, den persönlichen Assistenten, der ab diesem Zeitpunkt im Smartphone wohnte, umzubenennen. Und seit dem ersten Iron Man Film dürften viele Leute bei Sprachassistenten direkt an Jarvis denken. Mein Ziel war es also Snips in Jarvis umzubenennen – auch wenn das nichts an der etwas blechernen Stimme ändern würde und auch wenn ich danach immer noch keinen Superheldenanzug bauen könnte. „Hey Jarvis, wie wird das Wetter heute?“ klingt einfach viel cooler als „Hey Alexa“ oder „Hey Snips“.
Um ein eigenes Hotword festzulegen, muss der Assistent erst einmal trainiert werden. Laut Dokumentation ist das kein großer Aufwand und ein paar Terminalbefehle später begann ich mit der Aufzeichnung. Leider blieb die Kalibrierung immer bei der dritten Aufnahme hängen. Kein Problem, ich hatte ja Zeit, also nahm ich selber die nötigen Audiospuren auf. „Hey Jarvis“, „Hey Jarvis“, „Hey Jarvis“ – wenn man das drei Mal hintereinander zu seinem Computer sagt und nichts passiert, kommt man sich doch ein wenig verrückt vor.
Leider scheiterte auch dieser Versuch den Assistenten umzubenennen. Snips schien beharrlich bei diesem Namen bleiben zu wollen. Meine Vermutung ist, dass mein Mikrofon zu schlecht war. Sprachbefehle funktionieren nur aus einer sehr kurzen Entfernung und laut und deutlich ausgesprochen. Ich bin also jetzt auf der Suche nach einem leistungsfähigen Mikrofon, das überdies nicht zu sehr aufträgt. Der Plan ist nämlich dem MagicMirror, der immer noch parallel zu Snips läuft, einen ordentlich großen Bildschirm und einen guten Spiegel zu spendieren. Da sollten Lautsprecher und Mikrofon natürlich nicht auffallen. Vielleicht schaffe ich es ja Snips ein paar Komplimente beizubringen. „Spieglein, Spieglein an der Wand…“

Visual Regression Testing mit Puppeteer und Resemble.js (Teil 2)

In meinem letzten Artikel habe ich mit einem Beispiel beschrieben, wie man mittels Puppeteer automatisert Screenshots von Websites erstellt. Das Problem stellte sich für uns, weil wir im großen Umfang CMS-Unterseiten auf Frontend-Probleme nach einem Upgrade testen wollten.

Ziel war es, ein Skript zu erstellen, welches vor einem Upgrade gestartet werden kann und zunächst den Status Quo von Websites in Screenshots festhält.
Nachdem die Upgrades an den Websites durchgeführt wurden, kann das Skript erneut gestartet werden und es werden automatisch visuelle Vergleichtests durchgeführt.

Test-Logik

Zum Ablauf der Visual Regression Tests habe ich folgende kleine Test-Logik entwickelt:

Flowchart

Wenn ich keinen Screenshot finde, erstelle ich einen für einen späteren Vergleich. Finde ich einen vor, dann erstelle ich einen neuen und vergleiche ihn direkt.

Visual Regression Testing mit Puppeteer und Resemble.js

Um den obigen Test-Algorithmus abzubilden, muss die app.js aus meinem letzten Artikel um eine Testschleife, die zusätzliche Library Resemble.js und das File System-Modul von Node.js erweitert werden.

Die folgende Vorgehenswiese lässt sich auch anhand meiner Commits im Github-Repository nachverfolgen.

Dateizugriff einrichten

Für den Zugriff auf das Dateisystem stellt Node.js das fs-Modul bereit. Damit lassen sich klassische Dateioperationen durchführen (copy, move etc.).

Um das Modul zu verwenden, muss es mittels require in der app.js eingebunden werden:

const fs = require('fs')

Bisher war der Pfad- und Dateiname der Screenshots, die in der takeScreenshot()-Funktion erstellt werden noch hard coded. Weil die Funktion zukünftig sowohl Vorher- als auch Nachher-Screenshots festhalten soll, werden folgende Änderungen vorgenommen:

const screenshotsFolder = './screenshots/'

und

await page.screenshot({ path: filename, fullPage: true })

Test-Logik aufbauen

Jetzt kann die eigentliche Test-Logik aufgebaut werden. Ziel ist es, die Screenshots noch nicht zu vergleichen, aber schon die nötige Schleife zusammenzubasteln. Ich habe dafür eine Funktion erstellt, welche den Test startet. Hierfür eignet sich in diesem Fall die Verwendung der Immediately-invoked Function Expression.

Die Immediately-invoked Function Expression ist eine Möglichkeit, Funktionen sofort auszuführen, sobald sie erstellt werden:

(() => {
  /* Befehle */
})()

Unsere asynchrone Funktion sieht dann so aus:

// Immediately-invoked arrow function after launch
(async () => { 
    // Create screenshots folder if it does not exist
    if (!fs.existsSync(screenshotsFolder)) {
        fs.mkdir(screenshotsFolder, (err) => {
            if (err) throw err
        })
    }

    for (const website of websites) {
        const orgScreenshotPath = screenshotsFolder + website.filename + '.png'
        const testScreenshotPath = screenshotsFolder + website.filename + '_test.png'
        // Check if both original and testing screenshot already exist
        if (fs.existsSync(orgScreenshotPath) && fs.existsSync(testScreenshotPath)) {
            // Both exist run regressionTest()
        } else {
            if (fs.existsSync(orgScreenshotPath)) {
                // Original exists create test screenshot
                await takeScreenshot(website.url, testScreenshotPath)
                    .then(console.log('Created test: ' + website.filename))
                // run regressionTest()
            } else {
                // No Original exists, let's create a new one
                await takeScreenshot(website.url, orgScreenshotPath)
                    .then(console.log('Created original: ' + website.filename))
            }
        }
    }
})()

Mit fs.existsSync() wird geprüft, ob eine Datei unter dem angegeben Pfad existiert. Dies könnte auch mittels Promises/await asynchron und ohne Callbacks gemacht werden (Momentan noch experimental).

Vergleichen von Screenshots mit Resemble.js

Jetzt fehlt nur noch die regressionTest()-Funktion, damit wir unsere Tests durchführen können.

Hierfür muss zunächst Resemble.js mittels npm installiert und eingebunden werden:

$ npm install resemblejs --save

In unserer app.js:

const resemble = require('resemblejs')

Die asynchrone regressionTest()-Funktion sieht wie folgt aus:

const regressionTest = async (filename, orgScreenshotPath, testScreenshotPath) => {
    console.log('Visual Regression: ' +  filename)

    const diffFolder = screenshotsFolder + 'diff/'

    resemble(orgScreenshotPath).compareTo(testScreenshotPath).onComplete(data => {
        if (data.misMatchPercentage > 0) {
            console.log('Missmatch of ' + data.misMatchPercentage + '%')

            // Create screenshots/diff folder only when needed
            if (!fs.existsSync(diffFolder)) {
                fs.mkdir(diffFolder, (err) => {
                    if (err) throw err
                })
            }

            // Set filename and folder for Diff file
            const diffScreenshotPath = diffFolder + filename + '_' + data.misMatchPercentage + '_diff.png'
            fs.writeFile(diffScreenshotPath, data.getBuffer(), (err) => {
                if (err) throw err
            })
        }
    })
}

Die Funktion erhält die Parameter filename aus dem websites-Array, sowie den zusammengesetzten Pfad zum Original. Dazu kommt ein Vergleichsscreenshot (orgScreenshotPath, testScreenshotPath).

Diese werden nun durch resemble verglichen:

resemble(orgScreenshotPath).compareTo(testScreenshotPath)

Wenn ein Unterschied zwischen orgScreenshotPath und testScreenshotPath besteht wird eine Differenzgrafik erstellt. Diese zeigt standardmäßig die Unterschiede in Magenta an. Damit Fehler schneller gefunden werden können, werden diese Differenzbilder im Unterverzeichnisscreenshots/diff abgelegt.

In folgenden Screenshots fehlen Seiteninhalte. Resemble.js findet den Unterschied und stellt ihn gut sichtbar dar:

Resemble.js Animation

Schneller Vergleichen von einzelnen Screenshots

Wenn eine einzelne URL verglichen werden soll, ist es etwas mühselig diese immer in das websites-Array in app.js einzufügen. Deshalb habe ich in dem Skript die Möglichkeit ergänzt, beim Aufruf URL(s) als Kommandozeilenargumente anzuhängen:

$ node app.js https://rrzk.uni-koeln.de/

let websites = []

process.argv = process.argv.slice(2) // Slice away the first two command line arguments

if (process.argv.length == 0) { 
    // If no command line arguments are given add hardcoded examples
    websites = [
        { url: 'https://rrzk.uni-koeln.de/', filename: 'homepage' },
        { url: 'https://rrzk.uni-koeln.de/aktuelles.html', filename: 'news' },
        { url: 'https://typo3.uni-koeln.de/typo3-angebote.html', filename: 'typo3-offerings' },
        { url: 'https://typo3.uni-koeln.de/typo3-links-und-downloads.html', filename: 'typo3-links-and-downloads' }
    ]
} else {
    process.argv.forEach((val, index) => {
        try { // Check if argument is a URL
            let screenshotURL = new URL(val)
            // Add URL to websites array if valid and create filename
            websites.push({ url: screenshotURL.href, filename: index + '_' + screenshotURL.host})
        } catch (err) {
            console.error('"' + val + '" Is not a valid URL!')
        }
    })
}

Mittels der Klasse URL, wird die Zeichenkette in ein URL-Objekt konvertiert. Wenn dies fehlschlägt, enthält die Zeichenkette keine gültige URL.

Das finale app.js-Skript:

Das finale app.js Skript ist nun fertig app.js Download

Zum Starten einfach app.js ausführen: $ node app.js.

Wenn Screenshots von einzelnen Websites verglichen werden sollen, kann dies folgendermaßen gemacht werden:

$ node app.js https://rrzk.uni-koeln.de/
Created test: 0_rrzk.uni-koeln.de
Visual Regression: 0_rrzk.uni-koeln.de
Missmatch of 58.22%

Ausführen mittels Docker

Dieses Skript kann auch in einer containerbasierten Umgebung ausgeführt werden. Als Basis-Image verwende ich zenato/puppeteer. Das Image enthält einen standardisierten „Chrome“-Browser und stellt eine Umgebung bereit, in der Screenshots in konsistenter Weise erstellt werden.

Mein Dockerfile dafür sieht so aus:

FROM zenato/puppeteer:latest
USER root
COPY package.json /app/
COPY app.js /app/
RUN cd /app && npm install --quiet
WORKDIR /app
ENTRYPOINT [ "node" , "app.js" ]

Image erstellen:

$ docker build -t movd/puppeteer-resemble-testing:latest .

Container ausführen:

$ docker run --rm -v "${PWD}/screenshots:/app/screenshots" movd/puppeteer-resemble-testing:latest http://example.com

Das Image kann auch direkt vorgebaut von „Docker Hub“ geladen werden:

$ docker pull movd/puppeteer-resemble-testing:latest

Die hier erstellte Lösung basiert auf den Anforderungen im RRZK. Ich freue mich über Feedback und weitere Use-Cases, weil die Test-Logik eine einfache Schleife ist, ließe sich diese auch für andere Testreihenfolgen anpassen. Resemble.js ließe sich auch besonders gut in automatisierte Test mit Mocha oder Jest verwenden.

Windows auf dem Mac – Parallels vs. Bootcamp (und warum beides gleichzeitig nicht geht)

Früher oder später findet man als macOS Nutzer etwas, für das man ganz dringend Windows braucht. Die Alternativen für Mac sind entweder nicht gut genug, teuer oder gar nicht existent.

Ich muss gestehen, dass es bei mir in erster Linie ein Spiel war, das nicht für meinen Mac verfügbar war. Ich weiß, Schande über mich, dass ich versuche mit einem Mac zu zocken, aber ich habe nun einmal keinen anderen Rechner. Aus Kostengründen versuchte ich es zunächst mit Wine, was allerdings eher einem Glücksspiel glich. Mal funktioniert es, mal nicht. Schon alleine um Steam zu installieren, brauchte es drei verschiedene Programme von Wine, die abwechselnd funktionierten oder gar nicht erst starteten. Irgendwann vergeht einem dann die Lust. Also schaffte ich mir Parallels an. 

Parallels hat den Vorteil, dass macOS ganz bequem im Hintergrund laufen kann und nicht alle Aktionen in Windows ausgeführt werden müssen. Die Installation ist einfach, die Nutzung simpel und man kann sogar von Windows auf macOS-Dateien zugreifen und umgekehrt. Einziger Nachteil: man sollte einen Mac mit mindestens 8GB RAM haben, wenn man vernünftig damit arbeiten möchte. Es laufen eben zwei Betriebssysteme gleichzeitig, die sich den RAM teilen. Ein weiterer Nachteil, der für die meisten vielleicht unerheblich ist: Parallels unterstützt kein DirectX11. Da Apple die 3D-Grafikschnittstelle mit der Zeit komplett auf Metal umgestellt hat, funktioniert DirectX11 einfach nicht, wenn macOS im Hintergrund läuft. Was also tun?

Bootcamp heißt die zweite Möglichkeit, um Windows auf dem Mac zu nutzen. Das Einrichten ist etwas komplizierter, der Bootcamp-Installer aber schon auf dem Mac vorinstalliert. Je nach Modell des Macs braucht man einen leeren USB Stick und die ISO der gewünschten Windows Version. Bei neueren Macs läuft die Installation über eine kleine FAT32-Partition, auf die sowohl macOS als auch Windows zugreifen können und ein Stick ist nicht nötig.

Der Nachteil: man muss den Mac jedes Mal neu starten, wenn man Windows nutzen möchte und auf Dateien kann man nur von macOS zugreifen. Benötigt man unter Windows etwas von macOS heißt es neu starten und einen USB Stick suchen, oder einen Cloud-Dienst bemühen.

Zu Beginn der Installation von Bootcamp muss man sich entscheiden, wie groß die Windows-Partition werden soll.

Aber Vorsicht, wenn man auf Parallels nicht verzichten möchte bekommt man hier Probleme. Parallels scheint, was Speicherplatz angeht, ein wenig gierig zu sein. Auf meiner Festplatte waren über 200GB frei, Parallels nahm vom belegten Platz etwa 30GB ein. Trotzdem konnte ich im Bootcamp Installer nur 43GB zur Windows -Partition zuweisen. Zwei Stunden Googeln und einige Wutanfälle später war klar: Parallels reserviert sich den freien Festplattenspeicher vorsorglich. Wird dieser durch Programme oder Dateien unter macOS belegt ist das okay, doch beim Partitionieren der Festplatte legt Parallels Einspruch ein. Immerhin könnte es den Speicher ja irgendwann mal brauchen. Hier hilft dann nur der Verzicht auf Parallels oder das De- und Neuinstallieren, nachdem Bootcamp fertig ist. Backup nicht vergessen!

Lange Rede kurzer Sinn: Je nach Ansprüchen an den Arbeitsprozess können entweder Parallels oder Bootcamp die bessere Wahl sein. Auf beides gleichzeitig sollte man verzichten, es sei denn man stellt sich und seine Nerven auf einige zeitfressende Probleme ein.

Visuelle Tests von Websites nach Updates und Änderungen

An der Uni Köln wird seit mehreren Jahren auf das Content-Managment-System (CMS) TYPO3 gesetzt. Immer mehr Institute und universitäre Einrichtungen greifen dafür auf die TYPO3-Angebote des RRZK zurück, sodass aktuell über 50.000 Seiten, auf über 960 Domains bereitgestellt werden. CMS und Webapplikationen erfordern regelmäßige Wartung und Bugfixes. Jedoch birgt jede Änderung oder Korrektur die Gefahr, dass Nebenwirkungen auftreten und Fehler vielleicht an einer unerwarteten Stelle im System verursacht werden. Regressionstests haben die Aufgabe dies vorzubeugen und Fehler zu finden. Das Ziel lautet: Das, was vorher funktioniert hat, soll auch nach einem Upgrade funktionieren.

Als im vergangenen Sommer die Upgrades unserer Systeme auf die Long Term Support Version v8 des CMS anstanden, haben wir deshalb nach einer Lösung gesucht. Ziel war es ein Programm zu finden, mit der die Front-Ends der Websites auf mögliche “Macken” nach den Upgrades getestet werden können.

Im Web-Bereich wurden in den letzten Jahren einige Libraries und Tools für visuelle Regressionstest entwickelt. Ein visueller Regressionstest führt Front-End- oder User-Interfacetests durch, indem es die Screenshots des User-Interface erfasst und mit den Originalbildern vergleicht. Wenn ein neuer Screenshot vom Referenzscreenshot abweicht, warnt das visuelle Regressionstool.

Visual Regression Testing mit Puppeteer und Resemble.js

“Puppeteer Logo” erstellt von Google, geteilt gemäß der CC BY 3.0-Lizenz

Seit 2017 stellt das EntwicklerInnen-Team des Chrome-Browsers mit Puppeteer eine Open-Source Node.js Library bereit, mit welcher ein Chrome Browser ohne eine grafische Oberfläche über eine API angesteuert und automatisiert werden kann. Mittels Puppeteer lassen sich z.B. Screenshots von Websites erstellen aber auch Interaktionen mit dem Front-End einer Website simulieren.

Die mit Puppeteer erstellten Screenshots, gilt es zu vergleichen. Hierfür bietet sich die gut gepflegte Library Resemble.js an. Diese ist darauf spezialisiert Bilddateien abzugleichen und Unterschiede auszugeben.

Teil 1: Screenshots erzeugen

Mit folgenden Tutorial will ich zeigen wie man mit Puppeteer Screenshots mehrerer Seiten erstellt. Ziel ist es am Ende einen Ordner mit Screenshots zu haben, welche anschließend als Ausgangsbilder für Tests dienen werden.

In einem weiteren Artikel werde ich darauf eingehen, wie diese Screenshots mit einem späteren Zustand verglichen werden können.

Der Code zu dieser Anleitung ist auch auf GitHub zu finden. Link zum Stand dieses Artikels

Node.js und Puppeteer installieren

Vorraussetzung: Zum Ausführen wird die JavaScript Runtime node samt der mitgelieferten Packetverwaltung npm benötigt.

Installationsanleitung für verschiedene Betriebssysteme gibt es auf nodejs.org. Für die Verwendung in Linux-Distributionen bietet NodeSource Repositories mit kompilierten Binaries an.

Nachdem nun Node.js installiert ist, muss noch ein Ordner erstellt und in das Verzeichnis gewechselt werden:

mkdir puppeteer-resemble-testing
cd puppeteer-resemble-testing

Als nächstes muss ein neues Projekt erstellt werden:

npm init

Für den Zweck dieser Anleitung, kann jede Frage mittels der Return-Taste bestätigt werden. Dabei wird eine neue package.json erstellt.

Nun muss Puppeteer installiert werden:

npm install puppeteer --save

Die Installation kann eine kurze Weile dauern. Denn es wird auch automatisch eine Version von Chromium heruntergeladen und innerhalb des Projekts abgespeichert.

Einzelnen Screenshot erstellen

Puppeteer steht jetzt bereit und kann verwendet werden. Unser erstes Skript erzeugt einen Screenshot der kompletten RRZK-Startseite. Das Ergebnis wird als screenshot.png abgelegt.

Inhalt von app.js:

const puppeteer = require('puppeteer')

const takeScreenshot = async () => {
  const browser = await puppeteer.launch()
  const page = await browser.newPage()

  await page.setViewport({ width: 1920, height: 1080 })
  await page.goto('https://rrzk.uni-koeln.de/')
  await page.screenshot({ path: './screenshot.png', fullPage: true })
  await page.close()
  await browser.close()
}

takeScreenshot()

Puppeteer hat eine umfassende API-Dokumentation, alle obigen Parameter sind dort ausführlich beschrieben.

Puppeteer ist eine Promise-basierte Bibliothek, in diesem Fall bedeutet dies, dass die Aufrufe an die Chrome-Instanz asynchron durchgeführt werden. Damit der Code des Skript einfach zu lesen ist, wird async/await verwendet. Unsere takeScreenshot() Pfeilfunktion muss deshalb als async definiert werden.

Das Skript wird ausgeführt mit dem Befehl:

node app.js

Im folgenden werden wir auf dieses Skript aufbauen und nach und nach mehr Features hinzufügen.

Mehrere Screenshots in Stapelverarbeitung

Ziel ist es, weiterhin Screenshots vieler Seiten zu erstellen. Um etwas Ordnung zu waren, erstellen wir deshalb zunächst das Unterverzeichnis “screenshots”:

mkdir screenshots

Danach legen wir in app.js ein Array aus Objekten an. In einem Objekt wird jeweils die URL und der Screenshot-Dateiname hinterlegt.

const websites = [
  { url: 'https://rrzk.uni-koeln.de/', filename: 'homepage' },
  { url: 'https://rrzk.uni-koeln.de/aktuelles.html', filename: 'news' },
  { url: 'https://typo3.uni-koeln.de/typo3-angebote.html', filename: 'typo3-offerings'},
  { url: 'https://typo3.uni-koeln.de/typo3-links-und-downloads.html', filename: 'typo3-links-and-downloads'}
]

Zum Test kann dieses Array nun durchlaufen werden:

for (const website of websites) {
  console.log(website.url)
  console.log(website.filename)
}

Beim erneuten Ausführen des Skripts werden nun die Inhalte des websites-Arrays ausgegeben.

Bisher ist in unserer takeScreenshot() Funktion die URL und der Dateiname hartkodiert. Die Funktion muss jetzt mit url und filename gefüttert werden. Daraus ergeben sich folgende Änderungen:

const takeScreenshot = async (url, filename) => {
  const browser = await puppeteer.launch()
  const page = await browser.newPage()

  await page.setViewport({ width: 1920, height: 1080 })
  await page.goto(url)
  await page.screenshot({ path: './screenshots/' + filename + '.png', fullPage: true })
    .then(console.log('Screenshot: ' + filename))
  await page.close()
  await browser.close()
}

Fast geschafft, nun noch beim durchlaufen des Arrays die takeScreenshot()-Funktion aufrufen und die Werte übergeben:

for (const website of websites) {
  // console.log(website.url)
  // console.log(website.filename)
  takeScreenshot(website.url, website.filename)
}

Unsere finale kompakte app.js sieht dann wie folgt aus:

const puppeteer = require('puppeteer')

const websites = [
  { url: 'https://rrzk.uni-koeln.de/', filename: 'homepage' },
  { url: 'https://rrzk.uni-koeln.de/aktuelles.html', filename: 'news' },
  { url: 'https://typo3.uni-koeln.de/typo3-angebote.html', filename: 'typo3-offerings'},
  { url: 'https://typo3.uni-koeln.de/typo3-links-und-downloads.html', filename: 'typo3-links-and-downlods'}
]

const takeScreenshot = async (url, filename) => {
  const browser = await puppeteer.launch()
  const page = await browser.newPage()

  await page.setViewport({ width: 1920, height: 1080 })
  await page.goto(url)
  await page.screenshot({ path: './screenshots/' + filename + '.png', fullPage: true })
    .then(console.log('Screenshot: ' + filename))
  await page.close()
  await browser.close()
}

for (const website of websites) {
  // console.log(website.url)
  // console.log(website.filename)
  takeScreenshot(website.url, website.filename)
}

Nach dem Ausführen liegen nun unter „/screenshots“ vier PNG-Dateien.

Durch den .then Handler wird nach einlösen des Promise screenshot kurz Rückmeldung gegeben. Hiermit zeigt sich auch schön die asynchrone Arbeitsweise von Puppeteer.

In meinem nächsten Artikel werde ich das Skript um einen Vorher-Nachher-Vergleich mit Resemble.js erweitern, sowie das ganze mittels Docker so abpacken, dass man es einfach auf einem Linux-Server ausführen kann.

Erste IoT-Schritte: Laufende Temperaturmessung mit dem Arduino und ThingSpeak

Bei meinem letzten Beitrag zum Thema hatte ich schon beschrieben, wie man den Arduino mit Hilfe eines E32-Shields ins WLAN bringt. Nun sollte das Ganze einen nützlichen Zweck erfüllen. Für den Anfang reichte mir der Anschluss eines einfach DS18B20-Temperatursensors und die fortlaufende Dokumentation der Messung bei einem IoT-Server. Natürlich kann man auch selbst einen MQTT-Server (sprich: Mosquito-Server) aufsetzen und verwenden, aber ThingSpeak bietet sich hier einfach aus mehreren Gründen an. Vor allem auch deshalb, weil es bereits eine fertige Bibliothek für den Arduino gibt, sodass man die Befehle zum Senden der Werte auf einem hohen Abstraktionslevel belassen kann.

Wie so oft gilt auch hier: Natürlich gibt es das alles schon und es ist auch alles im Netz frei verfügbar und dokumentiert. Aber es kostet dann doch relativ viel Aufwand, alles zusammen zu tragen und im Detail zu verstehen. Daher schreibe ich meine Vorgehensweise hier strukturiert auf. Ebenfalls gilt: Natürlich braucht der wahre Profi den Arduino gar nicht dafür, ein kleiner ESP8266 genügt ebenso. Aber es geht ja auch ein wenig um den Spaß und um das Verständnis des Ganzen, und dafür ist der Arduino einfach besser geeignet. Natürlich wäre das Gleiche auch mit einem Raspberry Pi machbar, der große Vorteil hier liegt darin, dass man sich ein schönes Python-MQTT-Skript schreiben kann und die Fallen vom Arduino-C etwas umschifft.

Doch zurück zum Arduino-Beispiel. Die Verkabelung der Hardware ist – basierend auf der bereits bestehenden Kombination aus Arduino und ESP-Shield – denkbar einfach. Der Temperatursensor bringt drei Kabel mit, die an 3,3V- (rot), GND- (schwarz) und einen beliebigen Digital-PIN (gelb) des Arduino bzw. des aufgesteckten Shields angeschlossen werden. Fertig.

Die eigentliche Kunst liegt also in der Software. Als Basis nehme ich den verkürzten Sketch aus dem WLAN-Anschluss-Beispiel:

#include "WiFiEsp.h"
#include "SoftwareSerial.h"

SoftwareSerial Serial1(3, 2); // RX, TX

char ssid[] = "MeinTollesWLAN";
char pass[] = "**********";
int status = WL_IDLE_STATUS;
WiFiEspClient client;

void setup(void) {
Serial.begin(9600);
Serial1.begin(9600);
WiFi.init(&Serial1);

while ( status != WL_CONNECTED) {
Serial.print("Verbindungsaufbau zu ");
Serial.println(ssid);
status = WiFi.begin(ssid, pass);
}

Serial.println("Verbindung hergestellt!");
Serial.println();
printWLAN();
Serial.println();
}

void loop(void) {
}

void printWLAN()
{
IPAddress ip = WiFi.localIP();
Serial.print("IP-Adresse: ");
Serial.println(ip);
Serial.print("SSID: ");
Serial.println(WiFi.SSID());
long rssi = WiFi.RSSI();
Serial.print("Signalstaerke (RSSI): ");
Serial.println(rssi);
}

Wie man leicht sieht, macht der Sketch vorerst nichts abgesehen vom Verbindungsaufbau zum WLAN. Soweit so gut. Beginnen wir mit den Bibiotheken und Konstanten, die wir für alles Weitere benötigen. Die Bibliotheken heißen „OneWire“ (Temperatursensor) und „ThingSpeak“ (Verbindung zum IoT-Server).

ThingSpeak kann man auf der folgenden Seite herunterladen und dann der (sehr kurzen) Installationsanleitung folgen:

https://github.com/mathworks/thingspeak-arduino

Und wenn man gerade schon dabei ist, verfährt man ebenso mit der hier erhältlichen OneWire-Library.

Welche Konstanten werden nun benötigt? Zum einen die Nummer des PINs, an dem das gelbe Datenkabel des Sensors angeschlossen wurde. In meinem Beispiel ist das die Nr. 5. Um mit ThingSpeak arbeiten zu können, muss zudem ein Account bei dem Dienst angelegt werden. Nach dem Login kann dann ein einzelner Channel erstellt werden, der künftig die Daten entgegen nimmt. Die Nummer des Channels sowie unser APIKey von ThingSpeak sind die letzten benötigten Konstanten:

#include "OneWire.h"
#include "ThingSpeak.h"

int Sensor_Pin = 5;
unsigned long Channel = 123456789abcdef;
const char * APIKey = "************";

Bei unserer Messung verwenden wir ein Objekt aus der Klasse OneWire, dem als Parameter die Nummer des PINs übergeben wird:

OneWire ds(Sensor_Pin);

In der setup-Funktion wird die Kommunikation mit dem ThingSpeak-Server initialisiert, dabei wird die WLAN-Verbindung als Übertragungsweg übergeben:

ThingSpeak.begin(client);

Kommen wir zur Loop-Funktion. Diese soll im Grunde folgende Elemente enthalten: Messen, Ausgeben, Übertragen, Warten. Das Messen ist dabei mit riesigem Abstand die komplexeste Aufgabe und wird daher in eine eigene Funktion „getTemp“ ausgelagert. Der Rest ist relativ einfach. Damit nur echte Messwerte eingetragen werden, verwende ich „-100“ als Fehlerwert, alles darüber hinaus wird an ThingSpeak übertragen. Dabei müssen Channel, das Datenfeld (in unserem Fall einfach das einzige, also 1), der gemessene Wert sowie der APIKey übertragen werden. ThingSpeak kann man nicht mit beliebig vielen Werten fluten, 20 Sekunden Wartezeit zwischen den Messungen sind hier i.d.R. angemessen. Somit ergibt sich die Loop-Funktion:

void loop(void) {
float temperatur = getTemp();
Serial.println(temperatur);
if ( temperatur > -100) {
ThingSpeak.writeField(Channel, 1, temperatur, APIKey);
}
delay(20000);
}

Tja, und nun geht’s ans Eingemachte, namentlich um die Funktion „getTemp“. Ich gebe zu, dass ich – der ich nie wirklich C gelernt habe – dann doch einige Zeit intensiv darüber nachdenken musste, um die gefundenen Programmierbeispiele zu verstehen. Ich habe sie hier auf das Nötigste gekürzt und versuche sie zu erläutern.

Wir benötigen zwei Byte-Arrays namens „addr“ (für die Adressdaten des Sensors, es könnte mehrere geben) und „data“ (für die Messwerte). Zudem gilt es, ein paar Fehler abzufangen, z.B. Fehler in der Prüfsumme (CRC) oder gar einen nicht gefundenen oder nicht unterstützten Adapter. In all diesen Fällen wird unser Fehlerwert „-100“ zurückgegeben:


byte data[12];
byte addr[8];

if ( !ds.search(addr)) {
ds.reset_search();
return -100;
}

if ( OneWire::crc8( addr, 7) != addr[7]) {
Serial.println("CRC fehlerhaft!");
return -100;
}

if ( addr[0] != 0x10 && addr[0] != 0x28) {
Serial.print("Kein Sensor erkannt");
return -100;
}

Durch den Aufruf von „ds.search(addr)“ wird der Array praktischerweise direkt mit den Adressdaten des Sensors gefüllt, sodass wir nun – da keine Fehler aufgetreten sind – damit arbeiten können. Die nächsten Schritte sind im Einzelnen: Reset der Kommunikation, Auswahl des Sensors, Durchführen einer Messung und schließlich das Auslesen der Werte aus einem Zwischenspeicher, Speichern der Werte in unserem Datenarray. Anschließend kann wieder ein Reset der Suche nach Sensoren erfolgen.

ds.reset();
ds.select(addr);
ds.write(0x44); // Kommando: Messung durchfuehren
ds.reset();
ds.select(addr);
ds.write(0xBE); // Kommando: Werte auslesen
for (int i = 0; i < 9; i++) {
data[i] = ds.read();
}
ds.reset_search();

Fast fertig. Doch unsere Messwerte sind noch ein wenig „kryptisch“ und entsprechen nicht gerade dem, was wir aufzeichnen wollen. Die eigentlich interessanten Werte „MSB“ (most significant byte) und „LSB“ (least significant byte) stecken in unseren Datenfeldern 1 bzw. 0:

byte MSB = data[1];
byte LSB = data[0];

Sie enthalten die gemessene Temperatur in Binärdarstellung, wie ein Blick in das Datenblatt des DS18B20 verrät:

Um daraus nun einen „gewohnten“ Temperaturwert zu erhalten, bedarf es einer bitweisen Verschiebung des MSB um 8 Stellen nach links und einer bitweisen Verknüpfung mit dem LSB (und gleichzeitig einer Umwandlung in eine Fließkommazahl zur Basis 10):

float tempRead = ((MSB << 8) | LSB);

Wie man dem Datenblatt entnehmen kann, enthält das Ganze aber auch Nachkommastellen, das wurde bei der Umwandlung nicht berücksichtigt. Durch welche Zahl muss nun geteilt werden? Da unsere eigentliche „Basis“ (die 2^0 – Stelle) an vierter Position befindet, ist die Zahl um den Faktor 2^4 = 16 zu hoch. Es folgt:

float TemperatureSum = tempRead / 16;
return TemperatureSum;

Fertig! Hier noch einmal der komplette Sketch, viel Spaß beim Ausprobieren:


#include "OneWire.h"
#include "ThingSpeak.h"
#include "WiFiEsp.h"
#include "SoftwareSerial.h"

int Sensor_Pin = 5;
unsigned long Channel = 123456789abcdef;
const char * APIKey = "************";

OneWire ds(Sensor_Pin);

SoftwareSerial Serial1(3, 2); // RX, TX

char ssid[] = "MeinTollesWLAN";
char pass[] = "**********";
int status = WL_IDLE_STATUS;
WiFiEspClient client;

void setup(void) {
Serial.begin(9600);
Serial1.begin(9600);
WiFi.init(&Serial1);
ThingSpeak.begin(client);

while ( status != WL_CONNECTED) {
Serial.print("Verbindungsaufbau zu ");
Serial.println(ssid);
status = WiFi.begin(ssid, pass);
}

Serial.println("Verbindung hergestellt!");
Serial.println();
printWLAN();
Serial.println();
}

void loop(void) {
float temperatur = getTemp();
Serial.println(temperatur);
if ( temperatur > -100) {
ThingSpeak.writeField(Channel, 1, temperatur, APIKey);
}
delay(20000);
}

float getTemp(){

byte data[12];
byte addr[8];

if ( !ds.search(addr)) {
ds.reset_search();
return -100;
}

if ( OneWire::crc8( addr, 7) != addr[7]) {
Serial.println("CRC fehlerhaft!");
return -100;
}

if ( addr[0] != 0x10 && addr[0] != 0x28) {
Serial.print("Kein Sensor erkannt");
return -100;
}

ds.reset();
ds.select(addr);
ds.write(0x44); // Kommando: Messung durchfuehren
ds.reset();
ds.select(addr);
ds.write(0xBE); // Kommando: Werte auslesen

for (int i = 0; i < 9; i++) {
data[i] = ds.read();
}

ds.reset_search();

byte MSB = data[1];
byte LSB = data[0];

float tempRead = ((MSB << 8) | LSB);
float TemperatureSum = tempRead / 16;
return TemperatureSum;
}

void printWLAN()
{
IPAddress ip = WiFi.localIP();
Serial.print("IP-Adresse: ");
Serial.println(ip);
Serial.print("SSID: ");
Serial.println(WiFi.SSID());
long rssi = WiFi.RSSI();
Serial.print("Signalstaerke (RSSI): ");
Serial.println(rssi);
}

Probleme mit Time Machine und APFS unter High Sierra

Auf Macs mit interner SSD wird bei der Installation von High Sierra das Filesystem auf APFS geändert. Danach funktioniert u.a. Time Machine etwas anders. Es wird zuerst ein Snapshot angelegt, von dem dann das Backup gesichert wird. Nach erfolgreichem Backup sollte der Snapshot eigentlich wieder entfernt werden.
Das scheint nicht immer zu funktionieren. Bei mir wuchs unter macOS 10.13.2 die Zahl der Backups so lange, bis die interne SSD voll war. Ich gehe davon aus, dass das ein Bug ist, der in einem Update behoben wird. In der Zwischenzeit helfe ich mir mit diesem Shellscript:

% cat bin/tm-cleanup.sh
#!/bin/bash

mount | grep "Macintosh HD"| cut -f 1 -d' ' | xargs \ 
-n 1 umount

tmutil thinLocalSnapshots / 10000000000 4

Wenn man das Script mit

sudo bin/tm-cleanup.sh

aufruft, werden alle noch gemounteten Snapshots entmountet und danach entfernt.

NB: das funktioniert so nur, wenn die interne SSD den Namen „Macintosh HD“ trägt, was der Default ist. Ggf. muss der Name im Script angepasst werden.

ESP-12E WLAN-Shield auf dem Arduino Uno

Im Bestreben, einen Arduino Uno für diverse Zwecke drahtlos ans Netz zu bringen, stolperte ich bei EBay über das ESP-12E-Shield, welches selbst wiederum auf dem bekannten ESP8266-Microcontroller basiert. Incl. Versand aus dem fernen Osten bekommt man das gute Stück für unter 10 Euro, sodass ich es auf einen Versuch ankommen ließ. Einige Wochen später kam das Päckchen an, allerdings nur das Board selbst, ohne jeglichen Beipackzettel. Nun gut, das allwissende Internet hält ja mit Sicherheit entsprechendes Knowhow bereit. Wie ich dann feststellen musste: Jein. Wie so oft findet man alle nötigen Informationen, muss sie sich aber einigermaßen mühsam zusammenstellen und sortieren. Daher möchte ich hier das Verfahren nochmal sauber dokumentieren, für alle die vor dem gleichen Problem stehen.

Warnhinweis: Das hier beschriebene Verfahren habe ich selbst mit meinen genannten Bauteilen durchgeführt und es klappte problemlos und offenbar ohne Schäden. Dennoch keine Garantie, dass das immer und überall so sein muss. Also: Alles geschieht auf eigene Gefahr!

Schritt 1: Anpassung der Baud-Rate

Quelle: https://claus.bloggt.es/2017/01/14/using-esp8266-shield-esp-12e-elecshop-ml-by-wangtongze-with-an-arduino-uno/

Offensichtlich kommunizieren Arduino und ESP-12E bzw. ESP8266 deutlich stabiler mit einer Baud-Rate von 9600. Daher muss noch vor dem Einsatz des Shields diese über den Debug-Port umgestellt werden. Dazu nimmt man vier kleine Jumper Wire m/w und schließt sie wie folgt an, ohne das Shield auf den Arduino zu stecken:

Debug Port TX => Uno Pin 1 (TX)
Debug Port RX => Uno Pin 0 (RX)
Debug Port 5V => Uno 5V
Debug Port GND => Uno GND

Die vier Dip-Schalter auf dem ESP-12E können auf „Off“ bleiben. Anschließend den Arduino per USB an den Rechner anschließen, in der IDE die serielle Konsole aufrufen (Option „Both NL & CL“ und Baud-Rate 115200) und dieses Kommando durchgeben:

AT+UART_DEF=9600,8,1,0,0

Anschließend alles schließen und den Arduino wieder vom PC lösen. Die Kabel werden nicht mehr benötigt und können abgezogen werden.

Schritt 2: Shield aufstecken und anschließen

Quelle: https://arduino.stackexchange.com/questions/24919/how-to-connect-wi-fi-shield-esp-12e-esp8266-uart-wifi-wireless-shield-with-ardui

Das Shield wird nun wie vorgesehen auf den Arduino gesteckt, sodass alle Pins in die Steckerleisten des Arduino passen. Dann benötigen wir wieder zwei Jumper Wire m/w und verbinden:

Debug Port RX => Pin 2 des Arduino (der sich ja nun auf dem Shield selbst befindet)
Debug Port TX => Pin 3 des Arduino (der sich ja nun auf dem Shield selbst befindet)

Hier zur Verdeutlichung ein Foto:

Schritt 3: Nötige Library einbinden

Quelle: https://github.com/bportaluri/WiFiEsp/wiki

Den Arduino nun wieder per USB an den Rechner anschließen und die IDE aufrufen. Es wird die passende Bibliothek benötigt, damit man das WLAN-Modul ansteuern kann. Dazu das Menü „Sketch -> Include Library -> Manage Libraries…“ aufrufen, dort nach „WiFiESP“ suchen und das Paket „WiFiEsp by bportaluri“ installieren.

Schritt 4: Testskript anpassen und mit WLAN verbinden

Quelle: https://github.com/bportaluri/WiFiEsp/blob/master/examples/ConnectWPA/ConnectWPA.ino

Das nötige Testskript kann man hier finden. Einfach per Copy&Paste in einen neuen Sketch einfügen und die folgenden Zeilen anpassen:

Zeile 16 => hier die korrekten Ports eintragen, in unserem Fall „Serial1(3, 2)“
Zeile 19 => die SSID des eigenen WLANs eintragen
Zeile 20 => den Key des eigenen WLANs eintragen

Den Sketch nun kompilieren, hochladen und mit dem seriellen Monitor das Ergebnis beobachten. Idealerweise wird dort nun der Erfolg und die vom WLAN-Router erhaltene IP vermeldet.