
Flipper Zero Python CLI Wrapper
# PyFlipper
Flipper Zero Python CLI-Wrapper

## Artikel
- [Wie man ein Restaurant hackt](https://medium.com/@nic_whr/how-to-hack-a-restaurant-5d394be105a9)
## Funktionen und Eigenschaften:
- [x] Flipper serieller CLI-Wrapper
- [x] Websocket-Client-Schnittstelle
- [x] Einfache TCP-Schnittstelle
## Neues (Firmware 1.x Kompatibilität)
Diese Version bringt den Wrapper auf den aktuellen Stand der Flipper Zero CLI
(validiert auf echter Hardware, Firmware `unlshd-089e`):
- **Neues `info` Modul**: `flipper.info.device_info()` / `power_info()` /
`power_debug_info()` (die modernen `info device` / `info power` Befehle).
- **`power info` → `info power`**: `flipper.power.info()` funktioniert weiterhin und liest
jetzt im Hintergrund `info power`. Hinzugefügt: `flipper.power.otg_on()/otg_off()`.
- **`date`**: verwenden Sie `flipper.date.datetime()`; das Parsen wurde für das aktuelle
Ausgabeformat korrigiert.
- **Storage**: Größen werden sowohl als `KB` als auch `KiB` geparst, `read()` gibt jetzt
den vollständigen mehrzeiligen Dateiinhalt zurück, und exFAT / Karten ohne Label werden korrekt
geparst (behebt #21).
- **Infrared**: die Protokollliste wird live von der Firmware gelesen, daher werden `NECext`,
`Kaseikyo`, `RCA`, `Pioneer`, … automatisch unterstützt (behebt #15). Neu:
`flipper.ir.protocols()`.
- **`debug` → `sysctl debug`**, **`subghz tx/rx`** erhielt das erforderliche
`device`-Argument, **`bt hci_info`** unverändert.
- **NFC neu geschrieben** für die aktuelle interaktive Sub-Shell (`scan`, `dump`,
`emulate`, `field`, `apdu`, `raw`, `mfu`) mit pro-UID Mifare Classic Schlüssel-Cache-Helfern
(`import_nfc_keys`, `write_key_cache`).
- **Vereinheitlichte serielle Schicht** (seriell / Websocket / TCP) mit ANSI-Escape-Behandlung
für die aktuelle Shell, plus eine hardwarefreie Testsuite.
- **Aus der Standard-Firmware entfernt** (für benutzerdefinierte Firmwares, die sie noch ausliefern, beibehalten):
`ps` (→ `top`), `music_player`, `nfc detect`.
## Einrichtungsanweisungen:
```bash
$ pip install pyflipper
```
### Getestet auf:
- [x] Python 3.8.10 auf Linux 5.4.0 x86_64
- [x] Python 3.9.10 auf Windows 10
- [x] Python 3.10.5 auf Android 12 (Termux + [OTGSerial2WebSocket](https://play.google.com/store/apps/details?id=com.wh00hw.serial2websocket) KEIN ROOT ERFORDERLICH)
- [x] Python 3.12 auf Linux (Firmware `unlshd-089e`)
## Verwendung/Beispiele
### Verbindung
```python
from pyflipper import PyFlipper
# Lokaler serieller Port
flipper = PyFlipper(com="/dev/ttyACM0")
# ODER
# Remote serial2websocket Server
flipper = PyFlipper(ws="ws://192.168.1.5:1337")
# TCP
flipper = PyFlipper(tcp='192.168.89.222:22170')
```
### Power
```python
# Info (liest auf aktueller Firmware im Hintergrund `info power`)
info = flipper.power.info()
# Ausschalten
flipper.power.off()
# Neustarten
flipper.power.reboot()
# Neustarten im DFU-Modus
flipper.power.reboot2dfu()
# 5V OTG-Pin am GPIO-Header umschalten
flipper.power.otg_on()
flipper.power.otg_off()
```
### Update/Backup
```python
# Update von .fuf-Datei installieren
flipper.update.install(fuf_file="/ext/update.fuf")
# Flipper als .tar-Datei sichern
flipper.update.backup(dest_tar_file="/ext/backup.tar")
# Flipper aus einer Backup-.tar-Datei wiederherstellen
flipper.update.restore(bak_tar_file="/ext/backup.tar")
```
### Loader
```python
# Installierte Apps auflisten
apps = flipper.loader.list()
# App öffnen
flipper.loader.open(app_name="Clock")
```
### Flipper Info
```python
# Flipper-Datum abrufen (datetime-Objekt)
date = flipper.date.datetime()
# Flipper-Zeitstempel abrufen
timestamp = flipper.date.timestamp()
# Geräteinfo-Dict abrufen (aktuelle Firmware `info device`, durch Punkte getrennte Schlüssel)
device_info = flipper.info.device_info()
# Power-Info-Dict abrufen (aktuelle Firmware `info power`)
power_info = flipper.info.power_info()
# Erweiterte Power/Gauge-Debug-Informationen
power_debug = flipper.info.power_debug_info()
# Legacy `device_info` Befehl (durch Unterstriche getrennte Schlüssel, weiterhin unterstützt)
legacy_info = flipper.device_info.info()
# Heap-Info-Dict abrufen
heap = flipper.free.info()
# free_blocks-String abrufen
free_blocks = flipper.free.blocks()
# Bluetooth-Info abrufen
bt_info = flipper.bt.info()
```
> **Firmware-Kompatibilität.** Diese Bibliothek zielt nun auf die aktuelle Flipper Zero
> CLI (Firmware 1.x, verifiziert gegen Unleashed `unlshd-089e`). Einige Legacy-Befehle wurden
> aus der offiziellen Firmware entfernt und sind daher **nicht mehr verfügbar**: `ps` (ersetzt durch
> die interaktive `top`), `music_player` und `nfc detect` (NFC ist jetzt eine interaktive Sub-Shell).
> `power info` wurde zu `info power` verschoben, `debug` zu `sysctl debug`, und `storage` meldet
> Größen jetzt in `KiB` – alles wird von den obigen Wrappern transparent behandelt.
### Storage
#### Dateisystem-Info
```python
# Dateisystem-Info des Storages abrufen
ext_info = flipper.storage.info(fs="/ext")
```
#### Explorer
```python
# Storage /ext Dict abrufen
ext_list = flipper.storage.list(path="/ext")
# Storage /ext Tree-Dict abrufen
ext_tree = flipper.storage.tree(path="/ext")
# Dateiinfo abrufen
file_info = flipper.storage.stat(file="/ext/foo/bar.txt")
# Verzeichnis erstellen
flipper.storage.mkdir(new_dir="/ext/foo")
```
#### Dateien
```python
# Datei lesen
plain_text = flipper.storage.read(file="/ext/foo/bar.txt")
# Datei löschen
flipper.storage.remove(file="/ext/foo/bar.txt")
# Datei kopieren
flipper.storage.copy(src="/ext/foo/source.txt", dest="/ext/bar/destination.txt")
# Datei umbenennen
flipper.storage.rename(file="/ext/foo/bar.txt", new_file="/ext/foo/rab.txt")
# MD5-Hash einer Datei
md5_hash = flipper.storage.md5(file="/ext/foo/bar.txt")
# Datei in einem Block schreiben
file = "/ext/bar.txt"
text = """There are many variations of passages of Lorem Ipsum available,
but the majority have suffered alteration in some form, by injected humour,
or randomised words which don't look even slightly believable.
If you are going to use a passage of Lorem Ipsum,
you need to be sure there isn't anything embarrassing hidden in the middle of text.
"""
flipper.storage.write.file(path=file, text=text)
# Datei mit einem Listener schreiben
file = "/ext/foo.txt"
text_one = """There are many variations of passages of Lorem Ipsum available,
but the majority have suffered alteration in some form, by injected humour,
or randomised words which don't look even slightly believable.
If you are going to use a passage of Lorem Ipsum,
you need to be sure there isn't anything embarrassing hidden in the middle of text.
"""
flipper.storage.write.start(file)
time.sleep(2)
flipper.storage.write.send(text_one)
text_two = """All the Lorem Ipsum generators on the Internet tend to repeat predefined chunks as
necessary, making this the first true generator on the Internet.
It uses a dictionary of over 200 Latin words, combined with a handful of
model sentence structures, to generate Lorem Ipsum which looks reasonable.
The generated Lorem Ipsum is therefore always free from repetition, injected humour, or non-characteristic words etc.
"""
flipper.storage.write.send(text_two)
time.sleep(3)
# Nicht vergessen, zu stoppen
flipper.storage.write.stop()
```
### LED/Hintergrundbeleuchtung
```python
# Generische LED setzen (r,b,g,bl)
flipper.led.set(led='r', value=255)
# Blaue LED ausschalten
flipper.led.blue(value=0)
# Grüne LED auf Wert setzen
flipper.led.green(value=175)
# Hintergrundbeleuchtung einschalten
flipper.led.backlight_on()
# Hintergrundbeleuchtung ausschalten
flipper.led.backlight_off()
# LED ausschalten
flipper.led.off()
```
### Vibro
```python
# Vibro auf True oder False setzen
flipper.vibro.set(True)
# Vibro einschalten
flipper.vibro.on()
# Vibro ausschalten
flipper.vibro.off()
```
### GPIO
```python
# GPIO-Modus setzen: 0 - Eingang, 1 - Ausgang
flipper.gpio.mode(pin_name=PIN_NAME, value=1)
# GPIO-Pin-Wert setzen: 0 - aus, 1 - ein
flipper.gpio.set(pin_name=PIN_NAME, value=1)
# GPIO-Pin-Wert lesen
flipper.gpio.read(pin_name=PIN_NAME)
```
### MusicPlayer
> ⚠️ **Aus der offiziellen Firmware entfernt.** Der CLI-Befehl `music_player` wurde
> aus der Standard-Firmware entfernt (die App ist jetzt eine separate `.fap`). Diese Aufrufe
> funktionieren weiterhin auf benutzerdefinierten Firmwares, die den Befehl behalten (z. B. Unleashed /
> RogueMaster-Builds, die ihn ausliefern); auf Standard-Firmware geben sie eine
> "command not found"-Antwort zurück.
```python
# Lied im RTTTL-Format abspielen
rttl_song = "Littleroot Town - Pokemon:d=4,o=5,b=100:8c5,8f5,8g5,4a5,8p,8g5,8a5,8g5,8a5,8a#5,8p,4c6,8d6,8a5,8g5,8a5,8c#6,4d6,4e6,4d6,8a5,8g5,8f5,8e5,8f5,8a5,4d6,8d5,8e5,2f5,8c6,8a#5,8a#5,8a5,2f5,8d6,8a5,8a5,8g5,2f5,8p,8f5,8d5,8f5,8e5,4e5,8f5,8g5"
# In Schleife abspielen
flipper.music_player.play(rtttl_code=rttl_song)
# Schleife stoppen
flipper.music_player.stop()
# 20 Sekunden abspielen
flipper.music_player.play(rtttl_code=rttl_song, duration=20)
# Piepsen
flipper.music_player.beep()
# 5 Sekunden piepsen
flipper.music_player.beep(duration=5)
```
### NFC
Auf der aktuellen Firmware ist NFC eine interaktive **Sub-Shell** (`scanner`,
`dump`, `emulate`, `field`, `apdu`, `raw`, `mfu`). Dieser Wrapper betritt die Sub-Shell,
führt einen Befehl aus und verlässt sie wieder, sodass jede Methode ein normaler blockierender
Aufruf ist. `detect()` wird als Alias von `scan()` beibehalten.
```python
# Tag-Typ erkennen -> {'protocols': ['Mifare Classic'], 'raw': '...'} oder None
tag = flipper.nfc.scan(timeout=5)
# NFC-Feld für einige Sekunden einschalten
flipper.nfc.field(timeout=3)
# Eine APDU senden (ISO14443-4 / ISO15693-3); Protokoll wird bei Auslassung automatisch erkannt
resp = flipper.nfc.apdu(["00A4 04 00", "A0 00"], protocol="14_4a")
# Rohe Bytes mit angehängter CRC senden
flipper.nfc.raw("93 20", protocol="14a", crc=True)
# Mifare Ultralight Helfer
info = flipper.nfc.mfu_info()
block4 = flipper.nfc.mfu_read_block(4)
flipper.nfc.mfu_write_block(4, "00 01 02 03")
# Eine gespeicherte .nfc-Datei emulieren
flipper.nfc.emulate(file="/ext/nfc/mycard.nfc", timeout=10)
```
#### Auslesen einer Mifare Classic über die CLI
Der CLI-`dump` führt **keinen** Wörterbuchangriff durch: er liest Schlüssel aus einem
pro-UID-Cache unter `/ext/nfc/.cache/<UID>.keys` (der Datei, die die GUI nach einem erfolgreichen
Lesevorgang schreibt). Fehlt dieser Cache, gibt `dump` *"failed to read"* zurück.
Sie können ihn aus einem vorhandenen Flipper `.nfc`-Dump (neu) erstellen – die Schlüssel werden aus
jedem Sektortrailer übernommen:
```python
# Schlüssel aus einem nativen .nfc-Dump in den pro-UID-Schlüssel-Cache laden
uid, cache_path = flipper.nfc.import_nfc_keys("/path/to/card.nfc")
# Jetzt kann der CLI-Dump die Karte lesen (Protokoll-Token: mfc, mfu, 14_4a, ...)
flipper.nfc.dump(protocol="mfc", file="/ext/nfc/card_dump.nfc")
```
### RFID
```python
# Synchroner Standard-Timeout 5 Sekunden
# RFID lesen
rfid = flipper.rfid.read()
# RFID emulieren
emulated = flipper.rfid.emulate(key_type="EM4100", key_data="5500824806")
# RFID schreiben
written = flipper.rfid.write(key_type="EM4100", key_data="5500824806")
```
### SubGhz
```python
# Hex-Key N-mal senden (Standard count=10, device=0 -> CC1101_INT, 1 -> CC1101_EXT)
flipper.subghz.tx(hex_key="DEADBEEF", frequency=433920000, count=5, device=0)
# Empfangen (Standard frequency=433920000 device=0 raw=False timeout=5 Sekunden)
received = flipper.subghz.rx(frequency=433920000, device=0, raw=True, timeout=10)
# Aufgezeichnete Übertragung wiederholen
flipper.subghz.tx_from_file("/ext/subghz/foo.sub")
# Rohe .sub-Datei dekodieren
decoded = flipper.subghz.decode_raw(sub_file="/ext/subghz/foo.sub")
```
### Infrared
```python
# Die von der angeschlossenen Firmware unterstützten Protokolle auflisten (live aus der `ir`-Hilfe gelesen)
protocols = flipper.ir.protocols()
# Hex-Address und Hex-Command unter Auswahl eines Protokolls senden
flipper.ir.tx(protocol="Samsung32", hex_address="C000FFEE", hex_command="DEADBEEF")
# NECext wird jetzt unterstützt (Protokollliste wird von der Firmware gelesen, nicht hartcodiert)
flipper.ir.tx(protocol="NECext", hex_address="EF00", hex_command="FD02")
# Rohe Sendebeispiele
flipper.ir.tx_raw(frequency=38000, duty_cycle=0.33, samples=[1337, 8888, 3000, 5555])
# Synchroner Standard-Timeout 5 Sekunden
# Empfangen
r = flipper.ir.rx(timeout=10)
```
### IKEY
```python
# Lesen (Standard-Timeout 5 Sekunden)
ikey = flipper.ikey.read()
# Schreiben (Standard-Timeout 5 Sekunden)
flipper.ikey.write(key_type="Dallas", key_data="DEADBEEFCOOOFFEE")
# Emulieren (Standard-Timeout 5 Sekunden)
flipper.ikey.emulate(key_type="Dallas", key_data="DEADBEEFCOOOFFEE")
```
### Log
```python
# Ereignislogger anhängen (Standard-Timeout 10 Sekunden)
logs = flipper.log.attach()
```
### Debug
```python
# Debug-Modus aktivieren (gibt `sysctl debug 1` auf aktueller Firmware aus)
flipper.debug.on()
# Debug-Modus deaktivieren
flipper.debug.off()
```
### Onewire
```python
# Suchen
response = flipper.onewire.search()
```
### I2C
```python
# Abrufen
response = flipper.i2c.get()
```
### Input
```python
# Eingabe-Dump
dump = flipper.input.dump()
# Eingabe senden
flipper.input.send("up", "press")
```
## Tests
Die Testsuite ist zweigeteilt:
- **Hardwarefreie Tests** (`test_serial_wrapper.py`, `test_commands.py`) laufen
überall – sie treiben die Bibliothek gegen einen simulierten seriellen Port
(`tests/fake_serial.py`) an, der die Flipper-CLI-Rahmung nachbildet, sodass kein Gerät
erforderlich ist. Diese laufen in CI.
- **Hardware-Integrationstests** (`test_hardware.py`) sprechen mit einem echten Flipper und
werden **automatisch übersprungen**, wenn kein Gerät angeschlossen ist. Setzen Sie
`FLIPPER_COM=/dev/ttyACM0`, um einen bestimmten Port festzulegen.
```bash
# Alles (Hardware-Tests überspringen sich selbst, wenn kein Flipper angeschlossen ist)
python -m unittest discover -s tests -p "test_*.py" -v
# Nur hardwarefreie Tests
python -m unittest tests.test_serial_wrapper tests.test_commands -v
```
## Optimierungen
Sie können gerne auf jede Weise beitragen
- [ ] Queue-Thread-Orchestrator
- [ ] Alle CLI-Funktionen implementieren
- [ ] Async SubGhz Chat
## Lizenz
[MIT](https://choosealicense.com/licenses/mit/)
## Kauf mir ein Bier
**ZEC:** zs13zdde4mu5rj5yjm2kt6al5yxz2qjjjgxau9zaxs6np9ldxj65cepfyw55qvfp9v8cvd725f7tz7
**ETH:** 0xef3cF1Eb85382EdEEE10A2df2b348866a35C6A54
**BTC:** 15umRZXBzgUacwLVgpLPoa2gv7MyoTrKat
## Kontakte
- **Discord**: white_rabbit#4124
- **Twitter**: @nic_whr
- **GPG**: 0x94EDEADC