Magic Mirror automatisch ein- und ausschalten mit dem Abstandssensor VL53L0X

Magic Mirror automatisch ein- und ausschalten mit dem Abstandssensor VL53L0X

Für mein privates MagicMirror-Projekt möchte ich, dass sich das Display nur dann einschaltet, wenn eine Person mindestens ein bis zwei Sekunden lang davor steht und sich näher als 100 cm befindet. Um eine Person nur grob zu detektieren, würde ein Passive Infrared (PIR) Sensor ausreichen. Wenn man aber die genaue Position bzw. den Abstand der Person zum Display benötigt, um z.B. Fehleinschaltungen zu vermeiden, ist ein PIR-Sensor fehl am Platz. Ein Ultraschall-Sensor zur Abstandsmessung wäre da schon besser geeignet. Ich wollte jedoch etwas anderes ausprobieren.

UPDATE am 15.19.2019: Ich habe das Relais zum Ein- und Ausschalten des Bildschirms entfernt und verwende jetzt ein reine Software-Lösung (Details am Ende dieses Beitrags).

Messprinzip von Time-of-Flight-Sensoren

Um die Anwesenheit und den Abstand der Person zum Display sehr genau zu bestimmen eignen sich sogenannte Time-of-Flight (ToF) Sensoren. Diese ToF-Sensoren senden mittels LED oder Laser einen kurzen (unsichtbaren Infrarot-) Lichtimpuls aus, der von einem Objekt vor dem Sensor reflektiert und wieder empfangen wird. Anhand der hochgenau gemessenen Flugzeit des Lichtimpulses lässt sich die Entfernung des Objekts typischerweise mit einer Genauigkeit im Millimeter-Bereich bestimmen. Das Funktionsprinzip und der Zusammenhang zwischen Flugzeit und Abstand ist in der folgenden Abbildung erklärt.

Prinzip der Abstandsmessung
Prinzip der Abstandsmessung mittels Time-of-Flight-Sensor

Meine erste Idee war, den ToF-Sensor für den Beobachter unsichtbar hinter dem Spiegel anzubringen, wie in der folgenden Abbildung skizziert. Theoretisch sollte dies mit einem leistungsstarken ToF-Sensor bei ausreichend dünnem und nicht verunreinigten Glas prinzipiell funktionieren.

Prinzip der Personendetektion und Abstandsmessung
Prinzip der Personendetektion und Abstandsmessung mittels Time-of-Flight-Sensor

Wie ich durch mehrere Versuche herausgefunden habe, funktioniert der von mir verwendetet ToF-Sensor bei Anbringung hinter dem Spiegel leider nicht zufriedenstellend. Zum einen werden durch die Spiegelfolie der gesendete und empfangene Lichtimpuls so stark gedämpft, dass sie Sensitivität des Sensors zu gering ist und zum anderen wird der Lichtimpuls durch das Glas stark gestreut, was zu falschen Messergebnissen führt. Am Ende habe ich den Sensor deshalb an der Spiegelaussenseite angebracht. Dazu später mehr.

Anschluss des ToF-Sensors und Aktivierung des I2C-Busses

Ein kostengünstiger ToF-Sensor für Abstandsmessungen von ca. 50 bis 2000 Millimeter ist der VL53L0X von STMicroelectronics. Der Sensor selbst ist nur 4.4 x 2.4 x 1.0 mm groß und verwendet eine Laser-Diode, die in dem für den Menschen unsichtbaren Infrarot-Bereich mit einer Wellenlänge von 940 nm arbeitet. Der Standard-Entfernungsmessbereich geht von ca. 50 bis 1000 mm, die maximale messbare Entfernung unter optimalen Bedingungen im Long Range Mode beträgt 2000 mm. Für eigene Experimente empfiehlt es sich, einen auf ein Breadboard aufgelöteten Sensor zu kaufen, den es bereits ab ca. 10 Euro gibt.

Time-of-Flight-Sensor
Der verwendete Time-of-Flight-Sensor VL53L0X

Da die MagicMirror-Software bei mir auf einem Raspberry Pi läuft liegt es nahe, dass ich auch den ToF-Sensor an den selben Raspberry Pi anschließe sowie auch ein Relais, mit dem sich das Display des MagicMirrors ein- und ausschalten lässt. Der ToF-Sensor VL53L0X ist über die I2C-Schnittstelle und das Relais über einen GPIO-Pin angeschlossen. Die folgenden Abbildung zeigt den Schaltplan (anstelle des Displays ist ein Motor als Verbraucher dargestellt).

Abstandsmessung und Schalten eines Verbrauchers
Abstandsmessung mittels Time-of-Flight-Sensor VL53L0X und entfernungsabhängiges Schalten eines Verbrauchers

Zu beachten ist im Schaltplan die Verwendung von 5 V und 3.3 V als Spannung. Die beiden Relais auf der Relais-Platine, von denen nur eines benötigt wird, sind mittels Optokoppler galvanisch vom Raspberry Pi getrennt. Auf der Ansteuerungs- bzw. GPIO-Seite werden die Optokoppler mit 3.3 V versorgt. Auf der Seite an der das benötigte Relais angeschlossen ist, werden jedoch 5 V für den störungsfreien Betrieb benötigt. Ein interaktives Pinout des Raspberry Pi mit allen Anschlussbelegungen gibt es hier, nur für den Fall dass jemand noch andere Sensoren anschließen möchte.

Die folgende Abbildung zeigt die auf der Rückseite des Magic Mirrors angebrachten Hardwarekomponenten. Alle Details zum Bau des Magic Mirrors und zur Konfiguration der MagicMirror2-Software können in meinem MagicMirror-Beitrag nachgelesen werden.

Rückansicht der MagicMirror-Hardware
Rückansicht der verbauten MagicMirror-Hardware-Komponenten. Die Platinen wurden unter anderem mit doppelseitigem Klebeband mit Schaumstoffszwischenlage befestigt.

Den Time-of-Flight-Sensor habe ich, zugegebenermaßen leider etwas unschön, an der Vorderseite des Spiegels angebracht. Wenigstens ist er relativ klein, so dass er nicht sofort ins Auge sticht. Um dennoch einen besseren WAF zu erzielen, sollte ich mir aber noch eine bessere Lösung überlegen.

Magic Mirror mit Time-of-Flight-Sensor
Eingeschaltetes MagicMirror-Display. Unten am Glas ist der kleine Time-of-Flight-Sensor angebracht.

Um den Sensor mittels I2C anzusteuern, muss zuerst sichergestellt werden, dass am Raspberry Pi die entsprechenden Tools bzw. Treiber installiert sind. Dies lässt sich durch die folgenden Bash-Kommandos bewerkstelligen:

sudo apt-get update
sudo apt-get install -y python-smbus
sudo apt-get install -y i2c-tools

Anschließend muss noch der I2C-Bus am Raspberry Pi aktiviert werden. Dazu öffent man das Konfigurations-Fenster mittels folgendem Befehl:

sudo raspi-config

Man wählt zuerst den Menüpunkt Interfacing Options und danach I2C. Anschließend bestätigt man zweimal mit Yes um den I2C Bus zu aktivieren und automatisch zu laden. Danach sollte der Raspberry Pi neu gestartet werden:

sudo reboot

Um zu Überprüfen ob der I2C-Bus tatsächlich funktioniert und der angeschlossene VL53L0X-Sensor gefunden wird, gibt man folgendes Kommando ein:

sudo i2cdetect -y 1

Bei erfolgreicher Detektion des VL53L0X-Sensors, der standardmäßig die I2C-Adresse 0x29 besitzt, sollte man die folgende Bash-Ausgabe erhalten:

Bash-Ausgabe i2cdetect -y 1
Bash-Ausgabe nach der Eingabe des Befehls sudo i2cdetect -y 1

Ansteuerung und Test mittels Python

Um den Sensor anschließend mittels Python anzusprechen, benötigt man eine geeignet Bibliothek. Wenn man nach python VL53L0X googelt wird man schnell fündig. Ich habe mich für die Bibliothek von pimoroni entschieden, die auf GitHub verfügbar ist. Um sie zu installieren, gibt man in der Shell den folgenden Befehl ein:

sudo pip2 install git+https://github.com/pimoroni/VL53L0X_rasp_python.git

Wenn man auf seinem Raspberry Pi anstatt dem standardmäßigen Python 2.x bereits Python 3.x installiert hat, so tauscht man im obigen Kommando einfach pip2 mit pip3 aus. Die installierte Python-Version lässt sich mit diesem Befehl abfragen:

python --version

Zum Testen des Sensors mittels Python eignet sich der folgende Code:

import time
import VL53L0X

# --- Initialisierungen ---

# Erzeugen eines VL53L0X-Objekts
tofSensor = VL53L0X.VL53L0X()  
tofSensor.open()                 

# Sensor zur Entfernungsmessung vorbereiten
tofSensor.start_ranging(VL53L0X.Vl53l0xAccuracyMode.BETTER)

# --- Durchführen von 60 Messungen im Sekundenintervall ---
for count in range(1, 61):
    distance = tofSensor.get_distance()  # Messen der Entfernung
    if distance > 0.0 and distance < 2000.0:
        print("Messung %d: Gemessene Entfernung: %d mm" % (count,distance))
    else:
        print("Messung %d: Entfernung nicht messbar => kein Objekt vor dem Sensor" % (count))    
    
    time.sleep(1.0)                      # 1 Sekunde warten

tofSensor.stop_ranging()                 # Sensor deaktivieren
tofSensor.close()

Ich habe dieses Script als test_tof_sensor.py gespeichert. Dazu erstellt man mittels

nano test_tof_sensor.py

eine neue Datei, kopiert den obigen Python-Code hinein und speichert die Änderungen.

Um das Python-Script letztendlich ausführbar zu machen, müssen noch die Rechte entsprechend gesetzt werden:

sudo chmod +x test_tof_sensor.py

Das Python-Script wird dann wie folgt gestartet:

python test_tof_sensor.py

Die folgenden Abbildung zeigt eine typische Ausgabe. Jede Sekunde wird ein neuer Entfernungsmesswert ausgegeben.

Ergebnisse der ersten Testmessung
Ergebnisse der Testmessung, bei der der Abstand eines Objekts kontinuierlich von ca. 20 bis 80 cm vergrößert wurde.

Automatisches Ein- und Ausschalten des Displays

Um das Display des Magic Mirrors automatisch ein- und auszuschalten, wird noch ein Relais benötigt, das mittels GPIO-Pin 22 angesteuert wird (siehe Schaltplan weiter oben) und die Stromversorgung des Displays schaltet.

Meine persönlichen Anforderungen an das automatische Ein- und Ausschalten des Displays sind:

  • Um das Display einzuschalten, muss eine Person mindestens 1 bis 2 Sekunden lang in einem Abstand kleiner gleich 100 cm vor dem Display stehen.
  • So lange sich die Person vor dem Display befindet, soll es eingeschaltet bleiben.
  • Wenn keine Person mehr vor dem Display ist, soll es sich nach 60 Sekunden ausschalten.

Dazu habe ich das folgenden Python-Script mit dem Dateinamen mm_display_switch.py erstellt:

# coding: utf8

import time
import RPi.GPIO as GPIO         # Importieren der GPIO-Bibliothek
import VL53L0X                  # Importieren der ToF-Sensor-Bibliothek


# -----------------------------------------------------------------------------
# Initialisierungen
# -----------------------------------------------------------------------------

# --- GPIOs initialisieren ----------------------------------------------------
Relais = 22                     # Relais wird mit GPIO-Pin 22 angesteuert
GPIO.cleanup()
GPIO.setmode(GPIO.BCM)          # GPIOs über GPIO-Nummern ansprechen
GPIO.setwarnings(False)         # GPIO-Warnungen werden nicht ausgegeben
GPIO.setup(Relais, GPIO.OUT, initial=GPIO.HIGH)  # GPIO22 als Ausgang verwenden

# --- ToF-Sensor initialisieren -----------------------------------------------
tofSensor = VL53L0X.VL53L0X()  
tofSensor.open()                 
tofSensor.start_ranging(VL53L0X.Vl53l0xAccuracyMode.LONG_RANGE)

# --- Variablern initialisieren -----------------------------------------------
personDetect = 0                # noch keine Person detektiert
distanceThreshold = 1000.0      # Abstandsschwellwert in [mm]
screenOnTime = 60.0             # Zeit in [s], wie lange das Display mindestens
                                # eingeschaltet bleiben soll
measurementInterval = 1.0       # Messintervall in [s]    
GPIO.output(Relais, GPIO.HIGH)  # Relais/Bildschirm wird ausgeschaltet
                                # (Relais ist Active LOW, daher schaltet HIGH
                                # den Bildschirm aus und LOW ein)


# -----------------------------------------------------------------------------
# Eigentliches Programm in Endlosschleife
# -----------------------------------------------------------------------------

while True:
    
    # Messen der Entfernung bzw. des Abstands in [mm]
    distance = tofSensor.get_distance()
    print('Gemessene Entfernung: '+str(distance)+' mm')
    #print('Anzahl der Detektionen: '+str(personDetect))
    
    if distance <= 0.0:
        distance = distanceThreshold+1.0
    
    if distance <= distanceThreshold: personDetect = personDetect+1 #print('Eine Person wurde detektiert!') else: #print('Bildschirm wird ausgeschalten.') personDetect = 0 GPIO.output(Relais, GPIO.HIGH) # Bildschirm ausschalten if personDetect >= 2:
        #print('Bildschirm wird eingeschalten.')
        GPIO.output(Relais, GPIO.LOW)   # Bildschirm einschalten
        print('Nun wird 60 s lang gewartet')
        time.sleep(screenOnTime)        # 60 Sekunden lang warten
    
    time.sleep(measurementInterval)     # 1 Sekunde bis zur nächsten Messung
                                        # warten

Diese Python-Datei muss natürlich wieder ausführbar gemacht werden:

sudo chmod +x mm_display_switch.py

Eine weitere Anforderung ist, dass das Python-Script nach dem Booten oder Rebooten des Raspberry Pi automatisch ausgeführt wird. Dazu muss man einen entsprechenden Eintrag in der Datei rc.local machen:

sudo nano /etc/rc.local

In der Datei ergänzt man die folgende Zeile vor dem bereits vorhandenen exit 0 :

python /home/pi/Scripts/mm_display_switch.py &

Gegebenenfalls muss man das Verzeichnis anpassen, wenn sich das auszuführende Python-Script nicht in /home/pi/Scripts/ befindet. Das & ist wichtig damit die Datei rc.local weiter abgearbeitet wird und nicht auf das Beenden des Python-Scripts wartet. Besitzt das Python-Script nämlich eine Endlosschleife, wie dies in mm_display_switch.py der Fall ist, würde ewig gewartet werden und der Raspberry Pi beim Start hängenbleiben.

Zusätzliche Konfigurationen

Damit der Raspberry Pi beim Booten bei ausgeschalteten Display nicht automatisch die kleinste Auflösung einstellt, sollte diese auf einen festen Wert eingestellt werden. Für das verwendete Full-HD-Display ist die native und optimale Auflösung 1920×1080 Pixel. Um diese Einstellung durchzuführen öffnet man das Konfigurationstool mit dem Befehl

sudo raspi-config

Unter dem Menüpunkt Advanced OptionsResolution kann nun die Auflösung entsprechend eingestellt werden. Dies funktioniert auch, wenn man mittels ssh eingeloggt ist.

Auch die Leuchtdioden (LEDs) des Raspberry Pi können großteils deaktiviert werden, zum einen, um etwas Strom zu sparen (minimaler Effekt) und zum anderen, um ein unter Umständen störendes Durchscheinen der LEDs durch den Spiegel zu vermeiden. Dazu öffnet man mittels

sudo nano /boot/config.txt

die Konfigurationsdatei und fügt die folgenden Zeilen Code am Ende ein:

# Disable Ethernet LEDs
dtparam=eth_led0=14
dtparam=eth_led1=14

# Disable the PWR LED
dtparam=pwr_led_trigger=none
dtparam=pwr_led_activelow=off

# Disable the Activity LED
dtparam=act_led_trigger=none
dtparam=act_led_activelow=off

Die nichtdeaktivierbaren LEDs kann man bei Bedarf mit schwarzem Isolierband abkleben.

Zusammenfassung

Die MagicMirror2-Software und das selbsterstellte Python-Script für den ToF-Sensor laufen stabil. Das automatische entfernungsabhängige Ein- und Ausschalten des Displays mit Hilfe des ToF-Sensors und der Relais-Platine funktioniert sehr gut. Speziell bei viel Umgebungs-Sonnenlicht und dunkler Kleidung (= geringere Reflexion des Infrarot-Lichtimpulses) kann die Reichweite des Sensors etwas eingeschränkt sein. Unter diesen Bedingungen muss man dann schon mal etwas näher als 100 cm vor dem Spiegel stehen oder die Hand vor den Sensor halten, damit sich das Display einschaltet. Das stört aber nicht weiter.

Im Video seht ihr, wie sich das Display bei Annäherung automatisch einschaltet.

Update am 15.12.2019

Da das Relais während der letzten Wochen immer wieder Problem verursachte und nicht wie gewünscht schaltete, habe ich es entfernt. Eigentlich wollte ich stattdessen einen elektronischen Schalter mittels MOSFET realisieren. Das Problem hierbei ist jedoch, dass man einen sogenannten High Side Switch benötigt. Die verbreiteten Low Side Switches, wie z.B. der SparkFun MOSFET Power Control Kit sind, wie sich im praktischen Versuch herausgestellt hat, leider nicht geeignet. Der Grund ist, das die HDMI-Verbindung zwischen Raspberry Pi und Bildschirm eine gemeinsame Masse (GND) hat und ein Low Side Switch somit überbrückt wird.

Letztendlich schalte ich nun direkt über Bash-Befehle den Bildschirm ein oder in den Standby-Modus. Eine direkte Trennung von der Versorgungspannug erfolgt so zwar nicht, jedoch funktioniert diese Lösung problemlos und störungsfrei. Der höhere Standby-Stromverbrauch sollte sich in Grenzen halten.

Hier das aktualisierte Skript mm_display_switch.py:

# coding: utf8

import time
import VL53L0X                  # Importieren der ToF-Sensor-Bibliothek
import os                       # Importieren der System-Schnittstelle


# -----------------------------------------------------------------------------
# Initialisierungen
# -----------------------------------------------------------------------------

# --- ToF-Sensor initialisieren -----------------------------------------------
tofSensor = VL53L0X.VL53L0X()  
tofSensor.open()                 
tofSensor.start_ranging(VL53L0X.Vl53l0xAccuracyMode.LONG_RANGE)


# --- Funtionen zum Einschalten des Bildschirms via HDMI ----------------------
def bildschirm_ein():
    os.popen('vcgencmd display_power 1')

# --- Funtionen zum Ausschalten des Bildschirms via HDMI ----------------------
def bildschirm_aus(): 
    os.popen('vcgencmd display_power 0')
    

# --- Variablern initialisieren -----------------------------------------------
personDetect = 0                # noch keine Person detektiert
distanceThreshold = 1000.0      # Abstandsschwellwert in [mm]
screenOnTime = 60.0             # Zeit in [s], wie lange das Display mindestens
                                # eingeschaltet bleiben soll
measurementInterval = 1.0       # Messintervall in [s]    
bildschirm_aus()                # Relais/Bildschirm wird ausgeschaltet


# -----------------------------------------------------------------------------
# Eigentliches Programm in Endlosschleife
# -----------------------------------------------------------------------------

while True:
    
    # Messen der Entfernung bzw. des Abstands in [mm]
    distance = tofSensor.get_distance()
    
    if distance <= 0.0:
        distance = distanceThreshold+1.0
    
    if distance <= distanceThreshold:    
        personDetect = personDetect+1
    else:                                
        personDetect = 0
        bildschirm_aus()                # Bildschirm ausschalten
        
    if personDetect >= 2:
        bildschirm_ein()                # Bildschirm einschalten
        time.sleep(screenOnTime)        # 60 Sekunden lang warten
    
    time.sleep(measurementInterval)     # 1 Sekunde bis zur nächsten Messung
                                        # warten

 

Die Kommentare sind geschloßen.