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.12.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.
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.
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.
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).
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.
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.
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:
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.
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 Options → Resolution 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.
Bitte akzeptieren Sie Cookies von YouTube, um dieses Video abzuspielen. Wenn Sie akzeptieren, greifen Sie auf Inhalte von YouTube zu, einem Dienst, der von einem externen Dritten bereitgestellt wird.
Datenschutzerklärung und Nutzungsbedingungen von YouTube
Wenn Sie diesen Hinweis akzeptieren, wird ihre Wahl gespeichert und die Seite wird aktualisiert.
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