Photobox: GoPro + Raspberry Pi

Im Bekanntenkreis wurde geheiratet und ich weiß gar nicht so genau, ob sich das Brautpaar eine Photobox für die Feier gewünscht hatte, oder ob man sie damit überraschen wollte. Ist ja auch eigentlich egal: Jedenfalls war die Idee geboren und ich sah es nicht als all zu schwierig an, so ein Dingen zu bauen.
Im Kopf war das so ein "Kamera, Selbstauslöser, Raspberry Pi, ein bisschen löten und programmieren - geht ja schnell" Gedankengang. Das geht ja ratzefatz, 'n Sonntagnachmittag und fertig ist die Laube. Aber denkste!

Es hat ein bisschen gedauert, bis ich heraus fand, dass man eine GoPro via HTTP-Requests von außen ansprechen kann. Bei der Suche stolperte ich irgendwann über die Arbeit von Konrad Iturbe. Genauer gesagt über seine mühsam zusammengetragende, inoffizielle HTTP-Request-API über die verschiedenen GoPro-Modelle hinweg.
Nach etwas Suchen fand ich dort die Befehle zum Auslösen der Kamera, sowie das Auslesen aller verfügbaren Bilder auf der GoPro. Nun fehlte ja nur noch ein bisschen Hardware und Code. Anscheinend funktioniere ich unter Druck am effektivsten, denn zwischen der Idee und der Hochzeit lagen genau 6 Tage und unter der Woche ist da ja noch der "normale" Beruf. Am Tag der Übergabe (ein Tag vor der Hochzeit) schraubte ich Morgens, vor der Arbeit, noch das letzte, über Nacht gedruckte, Teil an. Eine 1A Punktlandung also.

Das Ziel war also klar: Die Gäste stellen sich vor die Kamera, drücken auf einen Knopf und das Bild wird gespeichert. Dafür fummelte ich mir erst einmal eine Testschaltung auf'm Steckbrett zusammen:

Zum Bau der Schaltung stehen folgende Dinge auf der Einkaufsliste:

  • 2 x Widerstand (330 Ohm)
  • 1 x Widerstand (10k Ohm)
  • 1 x Taster
  • 2 x LEDs (irgendwelche lustigen Farben)

Die LEDs waren zuerst nur als Spielerei gedacht, blieben aber zu Debugzwecken am Ende in der finalen Schaltung erhalten. Der Taster wird mittels PullUp-Widerstand befeuert, heißt: Der Eingang am Pi ist solange "High", bis der Taster gedrückt wird (dann Low).

Das Einlesen des Tasters, sowie das Ein-/Ausschalten der LEDs übernimmt ein NodeJS-Script. Dieses stellt auch gleichzeitig einen Previewstream der GoPro zur Verfügung, sodass die Gäste sich vor'm Fotografieren auch nochmal selbst sehen und sich zurechtrücken können. Der Stream der GoPro wird vom Script mittels FFmpeg transcodiert und auf einem Websocket zur Verfügung gestellt, sodass die Vorschau in einem Canvas-Element auf einer Webseite renderbar ist.

Designtechnisch ist der Kelch der Erkenntnis leider komplett an mir vorbei gegangen, aber dennoch hier mal ein Screenshot der Oberfläche:

Der obere Bereich ist die Vorschau, unten, in einer Preview, die letzten X Bilder.
Beim Auslösen wird zusätzlich ein zufälliger Text eingeblendet. Dieser kann in einem Configfile beliebig erweitert/angepasst werden.

Dummerweise gibt die GoPro leider keinerlei Response, wann und vorallem ob ausgelöst wurde. Deswegen wird der Request an die Kamera geschickt und dann zwei Sekunden gewartet. Das ist zwar ekelig, aber leider finde ich keine bessere Möglichkeit. In 99% der Tests schoss die GoPro in der Zeit das Photo und speicherte es auf die Karte.
Dannach wird dann eine Liste aller verfügbaren Bilder von der GoPro abgerufen, mit den bereits bekannten Bildern abgeglichen, die unbekannten, neuen Daten runtergeladen und dann letztendlich angezeigt.

Ich habe mal skizziert, wie was mit wem redet und welchen Weg die Daten nehmen:

Rot markiert ist der Kram, der macht, dass die Kamera auslöst. Blau skizziert die Antwort.
Die Photos liegen an zwei Stellen vor:

  • Auf der Kamera
  • Auf'm Raspberry (hier in zwei Versionen: Eine Kopie von der Kamera, sowie eine kleinere Vorschau)

Wer die benötigten 3D-Modelle sucht, der findet sie natürlich auf Thingiverse.

Wie installiert man das jetzt alles auf einem Raspberry Pi?



1) Vorbereiten der SD-Karte
Zuerst lädt man sich ein fertiges Image runter. Ich selber nutze hier Raspbian. Noobs und Co. funktionieren aber sicherlich auch. Das Abbild findet man hier: https://www.raspberrypi.org/downloads/raspbian/
Nach dem Entzippen muss das Image auf eine SD-Karte geschrieben werden. Auf'm Mac schaut man dazu erstmal mittels "Festplattendienstprogramm" nach, als was die Karte erkannt wird:

Nun wird die Karte "ausgeworfen" und danach beschrieben (disk2s1/disk2 anpassen!):

diskutil umount /dev/disk2s1
sudo dd bs=1m if=path_of_your_image.img of=/dev/disk2

Der Vorgang dauert ca. 10-15 Minuten. Nach Fertigstellung sollte sich das Script mit einer Info wie dieser zurückmelden:

3833+0 records in
3833+0 records out
4019191808 bytes transferred in 1188.257442 secs (3382425 bytes/sec)

Für Windows gibt es zum Erstellen der SD-Karte auch ein Tool mit grafischer Oberfläche: Win32DiskImager

Für weitere Infos schaut man am besten mal hier vorbei:
Linux: https://www.raspberrypi.org/documentation/installation/installing-images/linux.md
Windows: https://www.raspberrypi.org/documentation/installation/installing-images/windows.md
Mac: https://www.raspberrypi.org/documentation/installation/installing-images/mac.md


2) Booten und installieren
Beim Starten empfiehlt es sich einen Monitor anzuschließen, oder auf den eigenen Router zu schauen, welche IP der Raspberry vom Netzwerk bekommt. Mittels dieser kann man sich dann via SSH auf die Kiste einloggen. Windowsbenutzer nutzen dafür Putty, Mac/Linux-Benutzer können das direkt via Terminal.
Username: pi
Passwort: raspberry

Nun muss erst einmal das ganze System geupdatet werden:

sudo apt-get update
sudo apt-get upgrade

FFmpeg
Um den Vorschaustream nachher auf der Webseite sehen zu können, muss dieser transcodiert werden. Dazu nutz die Software FFMpeg. Diese muss allerdings selbst kompiliert werden.
Nun denn. Auf an's Werk:

sudo apt-get install libsdl-dev yasm
git clone https://github.com/FFmpeg/FFmpeg
cd FFmpeg
./configure
make -j4
sudo make install

Kleiner Hinweis: Hinter dem "make" ist der Parameter "-j4" zu sehen. Dieser führt beim Pi 2/3 dazu, auf allen vier Kernen gearbeitet wird. Ansonsten dauert das Ganze mehrere Stunden. Ich hatte erst nur einen 1er Pi. Das war echt nervig, aber die Zeit lässt sich prima nutzen, beispielsweise zum Schlafen.


NodeJs:
NodeJs! Das brauchen wir für den Shutter, die LEDs, den Stream und als Server für's Frontend.
Am besten installiert man sich dafür "n". Mit Hilfe dieser Software, ist es kinderleicht, die richtige Version zu installieren. Gebraucht wird irgendwas >= 6.0.0

git clone https://github.com/tj/n
sudo make install
sudo n 6.0.0

Wenn das durch ist, kann man zur Sicherheit die installierte Version abfragen. Die Antwort sollte v6.0.0 sein, oder eben höher:

pi@flazetonk ~/n $ node -v
v6.0.0

Quick2wire GPIO Admin:
Zusätzlich nutzt eins der NodeJS-Module die Software "quick2wire-gpio-admin".
Diese muss auch wieder kompiliert werden. Da die Entwicklung anscheinend eingestellt wurde und sich der Pfad zu den Ein-/Ausgängen des Pis während der Entwicklung geändert haben, hab ich das Projekt mal geforked und den Pfad angepasst. Das sollte nun wieder richtig funktionieren:

git clone git://github.com/flazer/quick2wire-gpio-admin.git
cd quick2wire-gpio-admin
make
sudo make install
sudo adduser $USER gpio

Nginx / Php (mit GD-Lib-Support):
Irgendwie muss ja auch dann die Webseite angezeigt werden und die Bilder von der GoPro geholt werden können.
Deswegen kommt noch ein Webserver und PHP mit in den Topf:

sudo apt-get install nginx php5-fpm php5-gd



3) WLAN einrichten
Die GoPro stellt einen Stream zur Verfügung, dazu muss man das WLan der Cam erst einmal anschalten und dann den Pi damit verbinden. Dazu trägt man die SSID und das Passwort in einer Config namens "wpa_supplicant.conf" ein:

sudo nano /etc/wpa_supplicant/wpa_supplicant.conf

In der Datei dann folgendes hinzufügen:  

network={
  ssid="SSID_DER_GOPRO"
  psk="PASSWORT_DER_GOPRO"
}

Wenn man das ganze auf einem Raspberry Pi 3 macht, empfiehlt es sich zusätzlich den Stromsparmodus des internen WLANs auszuschalten:

sudo nano /etc/network/interfaces

Folgendes hinzufügen:  
pre-up iw dev wlan0 set power_save off
post-down iw dev wlan0 set power_save on

Nun das Interface durchstarten:

sudo ifdown wlan0 && sudo ifup wlan0

Fertig. Das Interface sollte nun eine IP von der GoPro bekommen haben:

ifconfig wlan0
inet addr:10.5.5.100



4) Projekt auschecken und einrichten
Um es möglichst einfach zu halten, hab ich alles in ein Repository gesteckt. Das installiert man am besten unter /var/www/. Da kann dann auch der Webserver ganz easy drauf zugreifen.

git clone https://github.com/flazer/gopro-raspi-photobooth.git
cd photobooth/nodejs
npm install

Damit ist nun bereits das NodeJS fertig eingerichtet und bereits lauffähig. NPM hat alle Abhängigkeiten installiert. Geiles Zeugs!
Nun muss noch der Webserver eingerichtet werden:

sudo nano /etc/nginx/sites-available/default

Folgende Zeilen abändern:
root /var/www/html; -> root /var/www/photobooth/web;

index.php hinzufügen:
index index.php index.html index.htm index.nginx-debian.html;

Und Teile des Php-Blogs einkommentieren:
        location ~ \.php$ {
                include snippets/fastcgi-php.conf;
        #       # With php5-cgi alone:
        #       fastcgi_pass 127.0.0.1:9000;
                # With php5-fpm:
                fastcgi_pass unix:/var/run/php5-fpm.sock;
        }

        # deny access to .htaccess files, if Apache's document root
        # concurs with nginx's one
        #
        #location ~ /\.ht {
        #       deny all;
        #}

Das Zeugs speichern und den Webserver neustarten:

sudo /etc/init.d/nginx restart

Fast fertig. Jetzt muss noch die Konfiguration angepasst werden, damit die Webseite auch weiss, unter welcher IP sie den Stream und den Socket erreichen kann:

nano /var/www/photobox/web/config/system.conf.php

'stream' => [
        'ip' => 'RASPBERRY_PI_IP',
        'port' => '8084'
],

'socket' => [
        'ip' => 'RASPBERRY_PI_IP',
        'port' => '1337'
],

Damit der Webserver auch die Bilder auf'm Pi speichern kann, gibt man noch den Ordner für die Photos frei:

sudo chmod 0777 /var/www/photobooth/web/img/photos/*

FERTIG! Jetzt die GoPro auf "Picture" stellen, das NodeJs Skript starten,

node /var/www/photobooth/nodejs/index.js

und dann die IP des Raspberrys in den Browser eingeben.
TADA!



Bonus: Bilder vom Bau