Das ist die Anleitung für das Rätsel Infrarot-Buzzer.
Das Rätsel verwendet die Sensoren KY-006 (Passiver Piezo-Buzzer) und den KY-022 (Infrarot-Receiver).
Damit die Sensoren verwendet werden können, müssen die folgenden Schritte durchgeführt werden:
- Die Sensoren müssen an den Raspberry Pi angeschlossen werden. Dazu müssen die Sensoren an die GPIO-Pins angeschlossen werden. Die genaue Pin-Belegung ist in der Pin-Belegung zu finden. Das Schema für die Sensoren ist in der folgenden Tabelle zu finden:
KY-006_Passiver_Piezo-Buzzer
Raspberry Pi | Sensor |
---|---|
GPIO 24 [Pin 18] | Signal |
3,3V [Pin 1] | +V |
GND [Pin 6] | GND |
siehe: https://sensorkit.joy-it.net/de/sensors/ky-006
KY-022_Infrarot-Receiver
Raspberry Pi | Sensor |
---|---|
GPIO15 [Pin 10] | Signal |
3,3V [Pin 17] | +V |
GND [Pin 6] | GND |
siehe: https://sensorkit.joy-it.net/de/sensors/ky-022
Die Sensoren können auch an andere Pins angeschlossen werden. In diesem Fall müssen die Pins in der Konfiguration angepasst werden.
-
Die Konfiguration des Raspberry Pi muss angepasst werden. Dazu muss die Datei
/boot/config.txt
mit einem Texteditor geöffnet werden. Am Ende der Datei müssen die folgenden Zeilen hinzugefügt werden:dtoverlay=gpio-ir,gpio_pin=17
Die Zeile
dtoverlay=gpio-ir
aktiviert den Infrarot-Empfänger. Achtung, gpio_pin muss auf den Pin angepasst werden, an dem der Infrarot-Empfänger angeschlossen ist. In diesem Fall ist der Infrarot-Empfänger an Pin 17 angeschlossen.sudo reboot
Nach dem Neustart des Raspberry Pi sollte der Infrarot-Empfänger aktiviert sein.
sudo apt-get install ir-keytable -y
Mit dem Befehl
sudo ir-keytable
kann der Infrarot-Empfänger getestet werden. Wenn der Infrarot-Empfänger korrekt angeschlossen ist, sollte der Befehlsudo ir-keytable
die folgende Ausgabe liefern:Found /sys/class/rc/rc0/ (/dev/input/event0) with:
Wenn der Infrarot-Empfänger nicht korrekt angeschlossen ist, sollte der Befehl
sudo ir-keytable
die folgende Ausgabe liefern:No devices found
Wenn der Infrarot-Empfänger korrekt angeschlossen ist, kann der Infrarot-Empfänger mit dem Befehl
sudo ir-keytable -t -s rc0
getestet werden. Wichtig hierbei ist, dass dasrc0
auchrc1
oderrc2
sein kann. Die Ausgabe sollte wie folgt aussehen:Ereignisse werden getestet. Bitte drücken Sie STRG-C, um abzubrechen. 20495.413718: Lirc Protokoll(other): scancode = 0x25 umschalten=1 20495.413750: Ereignistyp EV_MSC(0x04): Scancode = 0x25 20495.413750: Ereignistyp EV_SYN(0x00). 20496.141707: Lirc Protokoll(other): scancode = 0x24 umschalten=1 20496.141736: Ereignistyp EV_MSC(0x04): Scancode = 0x24 20496.141736: Ereignistyp EV_SYN(0x00). 20496.877700: Lirc Protokoll(other): scancode = 0x26 umschalten=1 20496.877725: Ereignistyp EV_MSC(0x04): Scancode = 0x26 20496.877725: Ereignistyp EV_SYN(0x00). 20503.989638: Lirc Protokoll(other): scancode = 0xf umschalten=1 20503.989675: Ereignistyp EV_MSC(0x04): Scancode = 0x0f 20503.989675: Ereignistyp EV_SYN(0x00).
Wenn das nicht der Fall sein sollte, ist es möglich zu verwendeten Protokolle mit dem Befehl
sudo ir-keytable -c -p all -t -s rc0
zu auf "alle Protokolle" zu setzen.Jetzt kommt der schwierige Part.
Wichtig ist, dass ein Protokoll genutzt wird, welches zu dem Sender passt. Es ist unwahrscheinlich, dass ein Protokoll vorinstalliert ist, welches zu dem Sender passt. Aus diesem Grund ist es empfehlenswert eine eigenes Protokoll zu basteln. Wie solch ein Protokoll aussieht ist in der
samsung.toml
zu finden.Nun muss das erstellte Protokoll genutzt werden, um die Tasten des Senders zu erkennen. Dazu muss der Befehl
sudo ir-keytable -c -p <Protokoll> -t -s rc0
ausgeführt werden. Wichtig hierbei ist, dass dasrc0
auchrc1
oderrc2
sein kann.Beispiel:
sudo ir-keytable -c -p nec -w ./Desktop/sensor/samsung.toml -p imon -w ./Desktop/sensor/secret.toml -v -s rc1
ACHTUNG: HIER WERDEN 2 PROTKOLLE VERWENDET! Warum das so ist, wird im Lösungs-Teil erklärt.
Wenn nun eine Taste auf der Fernbedienung gedrückt wird, und das Protokoll richtig ist, sollte alles funktionieren.
Wichtig ist, dass die
MY_KEYMAP.toml
Datei bereit liegt, da sie für weitere Schritte benötigt wird.Weitere Informationen sind unter folgenden Links zu finden:
Das Rätsel teilt sich in 5 Dateien auf: escape.py
, events.py
, music.py
, sequence.py
und utils.py
.
Die escape.py
Datei ist die Hauptdatei. In dieser Datei wird das Spiel gestartet und die anderen Dateien importiert.
Die events.py
Datei enthält die Reihenfolge des Inputs auslesen, Musik abspielen usw.
Beispiel:
def toLongEvent(pwm) -> None:
play_tooLong_music(pwm)
console.clear()
console.print(
"Die Eingabe war zu lang. Versuche es erneut."
+ "\n"
+ ":bomb: Deine bisherige Eingabe wird gelöscht. :bomb:"
)
In diesem Beispiel wird die Funktion toLongEvent
definiert. Diese Funktion wird aufgerufen, wenn die Eingabe zu lang ist. In dieser Funktion wird die Musik abgespielt und eine Nachricht ausgegeben.
Die music.py
Datei enthält die Funktionen, welche die Musik abspielen und die enstprechenden Melodien (dict) enthalten.
Beispiel:
tooLong_music = {
'melody': [262, 294, 262, 294, 262],
'duration': 0.2,
'pause': 0.1,
}
#...
def play_tooLong_music(pwm) -> None:
"""Spielt die Melodie für zu lange Eingaben ab."""
play_music(melody =tooLong_music['melody'], duration=tooLong_music['duration'], pause=tooLong_music['pause'], pwm = pwm)
In diesem Beispiel wird die Melodie für zu lange Eingaben definiert. Diese Melodie wird in der Funktion play_tooLong_music
abgespielt.
Die sequence.py
Datei enthält die Funktionen, welche die Sequenz erstellt und ausließt.
ACHTUNG
Lass dich nicht von der seqGen()
Funktion verwirren. Diese ist absichtlich so geschrieben, dass sie nicht verständlich ist. Sie ist nur dafür da, dass die Sequenz nicht erraten werden kann.
Die "originale" seqGen()
Funktion ist als Kommentar in der Datei zu finden.
Die utils.py
Datei enthält die Funktionen, welche ab und zu benötigt werden.
Beispiel:
def int_check(s : str) -> bool:
"""Checks if the string s is an integer"""
try:
int(s)
except ValueError:
return False
else:
return True
In diesem Beispiel wird die Funktion int_check
definiert. Diese Funktion prüft, ob der String s
eine Zahl ist. Wenn ja, wird True
zurückgegeben, wenn nicht, wird False
zurückgegeben.
Der Ablauf des Programms ist in der escape.py
Datei zu finden.
def readInputEvent(device: InputDevice):
sequence = []
# Schleife, die auf Tastendruck wartet
for event in device.read_loop():
if event.type == ecodes.EV_KEY:
if event.value == 1:
data = categorize(event)
makeSignal(k2n(data.keycode), pwm)
# Ärgerlich, dass man pressed_key nicht lesen kann. Was können wir denn da machen?
pressed_key = obfuscated_func(data.keycode)
print("Du hast die Taste: '", pressed_key, "' gedrueckt")
sequence.append(data.keycode)
if len(sequence) > 10:
exc_event(pwm, 2)
sequence = []
# Was heißt denn [-1]?
if len(sequence) == 10 and sequence[-1] == "KEY_OK":
if is_correct_sequence(sequence, GPIO_P1N):
exc_event(pwm, 1)
else:
exc_event(pwm, 0)
sequence = []
In diesem Beispiel ist die Funktion readInputEvent
zu sehen. Diese Funktion liest die Eingabe des Spielers ein und speichert diese in der sequence
Variable. Wenn die Eingabe zu lang ist, wird die exc_event
Funktion mit dem Parameter 2
aufgerufen. Wenn die Eingabe richtig ist, wird die exc_event
Funktion mit dem Parameter 1
aufgerufen. Wenn die Eingabe falsch ist, wird die exc_event
Funktion mit dem Parameter 0
aufgerufen.
Wobei 0
für eine falsche Eingabe steht, 1
für eine richtige Eingabe und 2
für eine zu lange Eingabe.
Das wurde einfach nur so gemacht, dass bei einfachen lesen des Codes nicht direkt klar ist, was die Funktion macht.
Das eigentliche Rätsel besteht daraus, dass der Spieler die richtige Sequenz herausfinden muss. Dazu muss er die Tasten auf der Fernbedienung drücken. Wenn er die richtige Sequenz herausgefunden hat, muss er die OK Taste drücken. Wenn die Sequenz richtig ist, wird die exc_event
Funktion mit dem Parameter 1
aufgerufen. Wenn die Sequenz falsch ist, wird die exc_event
Funktion mit dem Parameter 0
aufgerufen.
Das Problem ist jedoch, dass die Sequenz nicht einfach so herausgefunden werden kann. Dazu muss der Spieler die seqGen
Funktion in der sequence.py
Datei verstehen. Diese Funktion ist jedoch absichtlich so geschrieben, dass sie nicht verständlich ist. Sie ist nur dafür da, dass die Sequenz nicht erraten werden kann.
Aber wir lösen wir nun das Rätsel?
Um die Lösung des Rätsels zu verstehen, müssen wir uns die Funktion is_correct_sequence
in der sequence.py
Datei anschauen.
def is_correct_sequence(sequence: list[str], expected_sequence: list[str]) -> bool:
"""Überprüft, ob die Sequenz korrekt ist
sequence: Die Sequenz, die überprüft werden soll
expected_sequence: Die erwartete Sequenz
Wenn die Sequenz korrekt ist, wird True zurückgegeben, ansonsten False
"""
x = ["S0VZX0E=", "S0VZX0I=", "S0VZX0M="]
if sequence == expected_sequence or any(
[u.b64decode(key).decode() in sequence for key in x]
):
return True
else:
return False
Dabei soll besonderes Augenmerk auf die Variable x
gelegt werden. Diese Variable enthält 3 Base64 Strings. Diese Strings sind die Lösung des Rätsels. Wenn der Spieler diese Strings in die Sequenz eingibt, wird die Sequenz als richtig erkannt.
Diese Strings sind die gemappten Daten aus der secret.toml
Datei, welche die Daten der Face ID enthält.
[[protocols]]
name = "secret"
protocol = "imon"
variant = "imon-pad"
[protocols.scancodes]
0x1fffffff = "KEY_A"
0x3fffffff = "KEY_B"
0x7fffffff = "KEY_C"
Wenn die Face ID Entsperrung an den Sensor gehalten wird, erhält der Sensor die Daten 0x1fffffff
, 0x3fffffff
und 0x7fffffff
. Diese Daten werden dann als KEY_A
, KEY_B
und KEY_C
gemappt. Dann Base64 encoded und in der x
Variable gespeichert. Das ist der Grund, warum die Face ID Entsperrung funktioniert.
Das Base64 Encoding muss manuell gemacht werden. Das könnte man noch automatisieren, aber du brauchst ja auch noch was zu tun. ;)