├── src └── pyflipper │ ├── lib │ ├── __init__.py │ ├── utils.py │ ├── i2c.py │ ├── onewire.py │ ├── debug.py │ ├── log.py │ ├── bt.py │ ├── free.py │ ├── vibro.py │ ├── date.py │ ├── music_player.py │ ├── gpio.py │ ├── ps.py │ ├── device_info.py │ ├── update.py │ ├── power.py │ ├── input.py │ ├── loader.py │ ├── threaded.py │ ├── led.py │ ├── nfc.py │ ├── subghz.py │ ├── ikey.py │ ├── rfid.py │ ├── ir.py │ ├── serial_wrapper.py │ └── storage.py │ ├── __init__.py │ └── pyflipper.py ├── setup.cfg ├── LICENSE ├── .github └── workflows │ └── pypi-release.yml ├── setup.py ├── .gitignore └── README.md /src/pyflipper/lib/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | version = attr: pyflipper.__version__ 3 | license_files = LICENSE -------------------------------------------------------------------------------- /src/pyflipper/__init__.py: -------------------------------------------------------------------------------- 1 | from . import lib, pyflipper 2 | from .pyflipper import PyFlipper 3 | 4 | __all__ = ['lib', 'pyflipper', 'PyFlipper'] -------------------------------------------------------------------------------- /src/pyflipper/lib/utils.py: -------------------------------------------------------------------------------- 1 | def is_hexstring(value: str) -> bool: 2 | try: 3 | int(value.replace(' ', ''), 16) 4 | return True 5 | except ValueError: 6 | return False -------------------------------------------------------------------------------- /src/pyflipper/lib/i2c.py: -------------------------------------------------------------------------------- 1 | class I2c: 2 | def __init__(self, serial_wrapper) -> None: 3 | self._serial_wrapper = serial_wrapper 4 | 5 | def get(self) -> str: 6 | return self._serial_wrapper.send("i2c") -------------------------------------------------------------------------------- /src/pyflipper/lib/onewire.py: -------------------------------------------------------------------------------- 1 | class Onewire: 2 | def __init__(self, serial_wrapper) -> None: 3 | self._serial_wrapper = serial_wrapper 4 | 5 | def search(self) -> str: 6 | return self._serial_wrapper.send("onewire search") -------------------------------------------------------------------------------- /src/pyflipper/lib/debug.py: -------------------------------------------------------------------------------- 1 | class Debug: 2 | def __init__(self, serial_wrapper) -> None: 3 | self._serial_wrapper = serial_wrapper 4 | 5 | def on(self) -> None: 6 | self._serial_wrapper.send("debug 1") 7 | 8 | def off(self) -> None: 9 | self._serial_wrapper.send("debug 0") -------------------------------------------------------------------------------- /src/pyflipper/lib/log.py: -------------------------------------------------------------------------------- 1 | from .threaded import Threaded 2 | 3 | class Log(Threaded): 4 | def __init__(self, serial_wrapper) -> None: 5 | self._serial_wrapper = serial_wrapper 6 | 7 | def attach(self, timeout: int = 10) -> str: 8 | self.exec(func=None, timeout=timeout) 9 | return self._serial_wrapper.send("log").rstrip("\r\n>:") -------------------------------------------------------------------------------- /src/pyflipper/lib/bt.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | class Bt: 4 | def __init__(self, serial_wrapper) -> None: 5 | self._serial_wrapper = serial_wrapper 6 | 7 | def info(self) -> dict: 8 | pattern = re.compile("(\w+):\s([\w|\d]+)") 9 | return {record[0]: int(record[1]) for record in pattern.findall(self._serial_wrapper.send("bt hci_info"))} 10 | -------------------------------------------------------------------------------- /src/pyflipper/lib/free.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | class Free: 4 | def __init__(self, serial_wrapper) -> None: 5 | self._serial_wrapper = serial_wrapper 6 | 7 | def info(self) -> dict: 8 | pattern = re.compile("([\w|\s]+):\s(\d+)") 9 | return {result[0].lower().replace(" ", "_").strip(): int(result[1]) for result in pattern.findall(self._serial_wrapper.send("free"))} 10 | 11 | def blocks(self) -> str: 12 | return self._serial_wrapper.send("free_blocks") -------------------------------------------------------------------------------- /src/pyflipper/lib/vibro.py: -------------------------------------------------------------------------------- 1 | class Vibro: 2 | def __init__(self, serial_wrapper) -> None: 3 | self._serial_wrapper = serial_wrapper 4 | 5 | def set(self, is_on: bool) -> None: 6 | assert isinstance(is_on, bool), \ 7 | "is_on must be boolean (True or False)." 8 | self._serial_wrapper.send(f"vibro {1 if is_on else 0}") 9 | 10 | def on(self) -> None: 11 | self.set(is_on=True) 12 | 13 | def off(self) -> None: 14 | self.set(is_on=False) -------------------------------------------------------------------------------- /src/pyflipper/lib/date.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | 3 | class Date: 4 | def __init__(self, serial_wrapper) -> None: 5 | self._serial_wrapper = serial_wrapper 6 | 7 | def date(self) -> datetime: 8 | result = self._serial_wrapper.send("date")[:-4] 9 | #FIXME: Flipper returns isoweekday 1-7 but %w is 0-6 10 | result = f"{result[:-1]}{int(result[-1])-1}" 11 | return datetime.strptime(result, "%Y-%m-%d %H:%M:%S %w") 12 | 13 | def timestamp(self) -> float: 14 | return self.date().timestamp() 15 | 16 | -------------------------------------------------------------------------------- /src/pyflipper/lib/music_player.py: -------------------------------------------------------------------------------- 1 | from .threaded import Threaded 2 | 3 | class MusicPlayer(Threaded): 4 | def __init__(self, serial_wrapper) -> None: 5 | self._serial_wrapper = serial_wrapper 6 | super().__init__() 7 | 8 | def play(self, rtttl_code: str, duration: int = 10): 9 | def _run(): 10 | self._serial_wrapper.send(f"music_player {rtttl_code}") 11 | self.exec(func=_run, timeout=duration) 12 | 13 | def beep(self, duration: float = 0.2): 14 | self.play("Beep:d=8,o=5,b=80:2b5", duration=duration) 15 | -------------------------------------------------------------------------------- /src/pyflipper/lib/gpio.py: -------------------------------------------------------------------------------- 1 | 2 | class Gpio: 3 | def __init__(self, serial_wrapper) -> None: 4 | self._serial_wrapper = serial_wrapper 5 | 6 | def mode(self, pin_name: str, value: int) -> None: 7 | #Set gpio mode: 0 - input, 1 - output 8 | self._serial_wrapper.send(f"gpio mode {pin_name} {value}") 9 | 10 | def set(self, pin_name: str, value: int) -> None: 11 | self._serial_wrapper.send(f"gpio set {pin_name} {value}") 12 | 13 | def read(self, pin_name: str) -> None: 14 | self._serial_wrapper.send(f"gpio read {pin_name}") -------------------------------------------------------------------------------- /src/pyflipper/lib/ps.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | class Ps: 4 | def __init__(self, serial_wrapper) -> None: 5 | self._serial_wrapper = serial_wrapper 6 | 7 | def list(self) -> list: 8 | pattern = re.compile( 9 | '(\w+)\s+(0[xX][0-9a-fA-F]+)\s+(\S+)\s+(\d+)\s+(\d+)') 10 | return [ 11 | {'Name': line[0], 12 | 'Stack start': line[1], 13 | 'Heap': int(line[2]), 14 | 'Stack': int(line[3]), 15 | 'Stack min free': int(line[4]) 16 | } for line in pattern.findall(self._serial_wrapper.send("ps")) 17 | ] 18 | -------------------------------------------------------------------------------- /src/pyflipper/lib/device_info.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | class DeviceInfo: 4 | def __init__(self, serial_wrapper) -> None: 5 | self._serial_wrapper = serial_wrapper 6 | 7 | def info(self) -> dict: 8 | pattern = re.compile("([\w|_]+)\s+:\s([\w|\d]+)") 9 | value = {} 10 | 11 | for x in pattern.findall(self._serial_wrapper.send("device_info")): 12 | if x[1].isdigit(): 13 | value[x[0]] = int(x[1]) 14 | 15 | elif x[1] in ["true", "false"]: 16 | value[x[0]] = eval(x[1].title()) 17 | 18 | else: 19 | value[x[0]] = x[1] 20 | 21 | return value -------------------------------------------------------------------------------- /src/pyflipper/lib/update.py: -------------------------------------------------------------------------------- 1 | class Update: 2 | def __init__(self, serial_wrapper) -> None: 3 | self._serial_wrapper = serial_wrapper 4 | 5 | def install(self, fuf_file: str) -> None: 6 | assert fuf_file.endswith('.fuf') 7 | return self._serial_wrapper.send(f"update install {fuf_file}") 8 | 9 | def backup(self, dest_tar_file: str) -> None: 10 | assert dest_tar_file.endswith('.tar') 11 | return self._serial_wrapper.send(f"update backup {dest_tar_file}") 12 | 13 | def restore(self, bak_tar_file: str) -> None: 14 | assert bak_tar_file.endswith('.tar') 15 | return self._serial_wrapper.send(f"update restore {bak_tar_file}") -------------------------------------------------------------------------------- /src/pyflipper/lib/power.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | class Power: 4 | def __init__(self, serial_wrapper) -> None: 5 | self._serial_wrapper = serial_wrapper 6 | 7 | def off(self) -> None: 8 | return self._serial_wrapper.send("power off") 9 | 10 | def reboot(self) -> None: 11 | return self._serial_wrapper.send("power reboot") 12 | 13 | def reboot2dfu(self) -> None: 14 | return self._serial_wrapper.send("power reboot2dfu") 15 | 16 | def info(self) -> dict: 17 | pattern = re.compile("([\w|_]+)\s+:\s([\w|\d]+)") 18 | items = pattern.findall(self._serial_wrapper.send("power info")) 19 | return {x: int(y) if y.isdigit() else y for x, y in items} 20 | 21 | -------------------------------------------------------------------------------- /src/pyflipper/lib/input.py: -------------------------------------------------------------------------------- 1 | from .threaded import Threaded 2 | 3 | class Input(Threaded): 4 | KEYS = ['up', 'down', 'left', 'right', 'back', 'ok'] 5 | TYPES = ['press', 'release', 'short', 'long'] 6 | 7 | def __init__(self, serial_wrapper) -> None: 8 | self._serial_wrapper = serial_wrapper 9 | 10 | def dump(self, timeout: int = 10) -> str: 11 | self.exec(func=None, timeout=timeout) 12 | return self._serial_wrapper.send("input dump") 13 | 14 | def send(self, key: str, type: str) -> None: 15 | assert key in self.KEYS, f"key must be in {self.KEYS}" 16 | assert type in self.TYPES, f"type must be in {self.TYPES}" 17 | #FIXME: 18 | self._serial_wrapper.send(f"input send {key} {type}") 19 | 20 | -------------------------------------------------------------------------------- /src/pyflipper/lib/loader.py: -------------------------------------------------------------------------------- 1 | 2 | class Loader: 3 | def __init__(self, serial_wrapper) -> None: 4 | self._serial_wrapper = serial_wrapper 5 | 6 | def list(self) -> dict: 7 | response = self._serial_wrapper.send("loader list") 8 | result = {} 9 | current_category = "" 10 | 11 | for line in response.split('\n'): 12 | if ':' in line: 13 | current_category = line[:-2] 14 | result[current_category] = [] 15 | else: 16 | value = line.strip() 17 | if value: 18 | result[current_category].append(value) 19 | 20 | return {x: y for x, y in result.items() if x or y} 21 | 22 | def open(self, app_name: str) -> None: 23 | self._serial_wrapper.send(f"loader open {app_name}") -------------------------------------------------------------------------------- /src/pyflipper/lib/threaded.py: -------------------------------------------------------------------------------- 1 | from threading import Thread 2 | import time 3 | 4 | 5 | class Threaded: 6 | def __init__(self) -> None: 7 | self.thread = None 8 | 9 | def exec(self, func, timeout: int): 10 | assert bool(func) or bool(timeout), "func or timeout required" 11 | def _timer(): 12 | time.sleep(timeout) 13 | self.stop() 14 | if func: 15 | #function thread execution 16 | self.thread = Thread(target=func) 17 | self.thread.start() 18 | if timeout: 19 | #force ctr-c when expires 20 | timer = Thread(target=_timer) 21 | timer.start() 22 | if not func: 23 | self.thread = timer 24 | 25 | def stop(self) -> None: 26 | if self.thread.is_alive(): 27 | self._serial_wrapper.ctrl_c() 28 | self.thread = None 29 | -------------------------------------------------------------------------------- /src/pyflipper/lib/led.py: -------------------------------------------------------------------------------- 1 | 2 | class Led: 3 | def __init__(self, serial_wrapper) -> None: 4 | self._serial_wrapper = serial_wrapper 5 | 6 | def set(self, led: str, value: int): 7 | assert 0 <= value <= 255, "Value must be in 0-255 range." 8 | assert led in ("r", "b", "g", "bl"), \ 9 | "Color must be 'r', 'b', 'g' or 'bl' for backlight." 10 | 11 | self._serial_wrapper.send(f"led {led} {value}") 12 | 13 | def red(self, value: int) -> None: 14 | self.set(led='r', value=value) 15 | 16 | def green(self, value: int) -> None: 17 | self.set(led='g', value=value) 18 | 19 | def blue(self, value: int) -> None: 20 | self.set(led='b', value=value) 21 | 22 | def off(self) -> None: 23 | self.red(value=0) 24 | self.green(value=0) 25 | self.blue(value=0) 26 | 27 | def backlight_on(self) -> None: 28 | self.set(led='bl', value=255) 29 | 30 | def backlight_off(self) -> None: 31 | self.set(led='bl', value=0) 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 wh00hw 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.github/workflows/pypi-release.yml: -------------------------------------------------------------------------------- 1 | name: Publish Python package and GitHub Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v0.21' 7 | 8 | jobs: 9 | release: 10 | name: Build, Release and Upload to PyPI 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - name: Checkout repository 15 | uses: actions/checkout@v4 16 | 17 | - name: Set up Python 18 | uses: actions/setup-python@v5 19 | with: 20 | python-version: '3.11' 21 | 22 | - name: Install dependencies 23 | run: | 24 | python -m pip install --upgrade pip 25 | pip install build twine 26 | 27 | - name: Build the package 28 | run: python -m build 29 | 30 | - name: Create GitHub Release 31 | uses: softprops/action-gh-release@v2 32 | with: 33 | tag_name: ${{ github.ref_name }} 34 | name: Release ${{ github.ref_name }} 35 | files: | 36 | dist/*.whl 37 | dist/*.tar.gz 38 | env: 39 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 40 | 41 | - name: Publish to PyPI 42 | env: 43 | TWINE_USERNAME: __token__ 44 | TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }} 45 | run: twine upload dist/* 46 | -------------------------------------------------------------------------------- /src/pyflipper/lib/nfc.py: -------------------------------------------------------------------------------- 1 | import re 2 | from .threaded import Threaded 3 | 4 | class NFC(Threaded): 5 | def __init__(self, serial_wrapper) -> None: 6 | self._serial_wrapper = serial_wrapper 7 | 8 | def detect(self, timeout: int = 5) -> dict: 9 | def _run(): 10 | p = re.compile( 11 | "found:\s([A-Z|\-]+)\sUID\slength:\s(\d+),\sUID:([\w|\d]+)") 12 | try: 13 | x = re.search(p, self._serial_wrapper.send("nfc detect")).groups() 14 | return {'type': x[0], 'lenght': x[1], 'UID': x[2]} 15 | except Exception: 16 | return None 17 | self.exec(func=None, timeout=timeout) 18 | return _run() 19 | 20 | def emulate(self, timeout: int = 5) -> None: 21 | def _run() -> str: 22 | data = self._serial_wrapper.send("nfc emulate") 23 | #TODO parse data 24 | return data 25 | self.exec(func=None, timeout=timeout) 26 | return _run() 27 | 28 | def field(self, timeout: int = 5) -> None: 29 | def _run() -> str: 30 | data = self._serial_wrapper.send("nfc field") 31 | #TODO parse data 32 | return data 33 | self.exec(func=None, timeout=timeout) 34 | return _run() 35 | -------------------------------------------------------------------------------- /src/pyflipper/lib/subghz.py: -------------------------------------------------------------------------------- 1 | from .threaded import Threaded 2 | 3 | class Subghz(Threaded): 4 | def __init__(self, serial_wrapper) -> None: 5 | self._serial_wrapper = serial_wrapper 6 | 7 | def tx(self, hex_key: str, frequency: int = 433920000, te: int = 403, count: int = 10): 8 | # TODO: params assertion and check if default frequency is allowed worldwide 9 | return self._serial_wrapper.send(f"subghz tx {hex_key} {frequency} {te} {count}") 10 | 11 | def tx_from_file(self, file_name: str, repeat: int = 1, device: int = 0): 12 | assert repeat > 0 13 | assert device in (0, 1) # 0 => CC1101_INT, 1 => CC1101_EXT 14 | return self._serial_wrapper.send(f"subghz tx_from_file {file_name} {repeat} {device}") 15 | 16 | def rx(self, frequency: int = 433920000, raw: bool = False, timeout: int = 5): 17 | # TODO: params assertion and check if default frequency is allowed worldwide 18 | cmd = "rx" if not raw else "rx_raw" 19 | def _run(): 20 | data = self._serial_wrapper.send(f"subghz {cmd} {frequency}") 21 | #TODO: Parse data 22 | return data 23 | self.exec(func=None, timeout=timeout) 24 | return _run() 25 | 26 | def decode_raw(self, sub_file: str) -> str: 27 | # TODO: implement regex catch errors 28 | assert sub_file.endswith('.sub') 29 | return self._serial_wrapper.send(f"subghz decode_raw {sub_file}") 30 | 31 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | 2 | import setuptools 3 | 4 | with open('README.md', 'r', encoding='utf-8') as fh: 5 | readme = fh.read() 6 | 7 | setuptools.setup( 8 | name = 'pyflipper', 9 | package_dir={'': 'src'}, 10 | packages=setuptools.find_packages(where='src'), 11 | version = '0.21', 12 | license='MIT', 13 | long_description=readme, 14 | long_description_content_type='text/markdown', 15 | description = 'Flipper Zero cli wrapper', 16 | author = 'wh00hw', 17 | author_email = 'white_rabbit@autistici.org', 18 | url = 'https://github.com/wh00hw/pyFlipper', 19 | project_urls={ 20 | 'Documentation': 'https://github.com/wh00hw/pyFlipper/blob/master/README.md', 21 | 'Bug Reports': 22 | 'https://github.com/wh00hw/pyFlipper/issues', 23 | 'Source Code': 'https://github.com/wh00hw/pyFlipper', 24 | }, 25 | keywords = ['flipper', 'wrapper', 'module'], 26 | install_requires=[ 27 | 'pyserial', 28 | 'websocket-client', 29 | ], 30 | classifiers=[ 31 | # see https://pypi.org/classifiers/ 32 | 'Development Status :: 4 - Beta', 33 | 34 | 'Intended Audience :: Developers', 35 | 'Topic :: Software Development :: Build Tools', 36 | 37 | 'Programming Language :: Python :: 3.8', 38 | 'Programming Language :: Python :: 3.9', 39 | 'Programming Language :: Python :: 3.10', 40 | 'License :: OSI Approved :: MIT License', 41 | 'Operating System :: OS Independent', 42 | ], 43 | python_requires='>=3.8', 44 | ) 45 | -------------------------------------------------------------------------------- /src/pyflipper/lib/ikey.py: -------------------------------------------------------------------------------- 1 | from .utils import is_hexstring 2 | from .threaded import Threaded 3 | 4 | class Ikey(Threaded): 5 | KEY_TYPES_TO_KEY_DATA_LENGHT = {"Dallas": 8, "Cyfral": 2, "Metakom": 4} 6 | 7 | def __init__(self, serial_wrapper) -> None: 8 | self._serial_wrapper = serial_wrapper 9 | 10 | def _validations(self, key_type, key_data): 11 | key_types = list(self.KEY_TYPES_TO_KEY_DATA_LENGHT.keys()) 12 | assert key_type in key_types, f"key_type must be in {key_types}" 13 | assert is_hexstring(key_data), "key_data must be hexstring" 14 | assert len(key_data.replace(' ', '')) == self.KEY_TYPES_TO_KEY_DATA_LENGHT[key_type] 15 | 16 | def read(self, timeout: int = 5) -> str: 17 | def _run(): 18 | data = self._serial_wrapper.send(f"ikey read") 19 | #TODO: Parse data 20 | return data 21 | self.exec(func=None, timeout=timeout) 22 | return _run() 23 | 24 | def write(self, key_type: str, key_data: str, timeout: int = 5) -> str: 25 | self._validations(key_type=key_type, key_data=key_data) 26 | def _run(): 27 | data = self._serial_wrapper.send(f"ikey write {key_type} {key_data}") 28 | #TODO: Parse data 29 | return data 30 | self.exec(func=None, timeout=timeout) 31 | return _run() 32 | 33 | def emulate(self, key_type: str, key_data: str, timeout: int = 5) -> None: 34 | self._validations(key_type=key_type, key_data=key_data) 35 | self.exec(func=None, timeout=timeout) 36 | self._serial_wrapper.send(f"ikey emulate {key_type} {key_data}") 37 | -------------------------------------------------------------------------------- /src/pyflipper/lib/rfid.py: -------------------------------------------------------------------------------- 1 | from .threaded import Threaded 2 | 3 | class RFID(Threaded): 4 | KEY_TYPES = ["EM4100", "H10301", "Indala26", "IoProxXSF", "AWID", "FDX-A", "FDX-B", "HIDProx", "HIDExt", "Pyramid", "Viking", "Jablotron", "Paradox", "PAC/Stanley", "Keri", "Gallagher"] 5 | def __init__(self, serial_wrapper) -> None: 6 | self._serial_wrapper = serial_wrapper 7 | 8 | def read(self, timeout: int = 5) -> str: 9 | def _run(): 10 | data = self._serial_wrapper.send("rfid read") 11 | #TODO: Parse data 12 | return data 13 | self.exec(func=None, timeout=timeout) 14 | return _run() 15 | 16 | # key_type can be one of EM4100, H10301, Indala26, IoProxXSF, AWID, FDX-A, FDX-B, HIDProx, HIDExt, Pyramid, Viking, Jablotron, Paradox, PAC/Stanley, Keri, Gallagher 17 | # key_data is just the data you can also read from the lf rfid tags in hex eg '5500824806' from EM4100 type 18 | 19 | def emulate(self, key_type, key_data, timeout: int = 5) -> str: 20 | assert key_type in self.KEY_TYPES, f"key_type not in {str(self.KEY_TYPES)}" 21 | def _run(): 22 | data = self._serial_wrapper.send("rfid emulate " + key_type + " " + key_data) 23 | 24 | return data 25 | self.exec(func=None, timeout=timeout) 26 | return _run() 27 | 28 | 29 | def write(self, key_type, key_data, timeout: int = 5) -> str: 30 | assert key_type in self.KEY_TYPES, f"key_type not in {str(self.KEY_TYPES)}" 31 | def _run(): 32 | data = self._serial_wrapper.send("rfid write " + key_type + " " + key_data) 33 | 34 | return data 35 | self.exec(func=None, timeout=timeout) 36 | return _run() 37 | 38 | -------------------------------------------------------------------------------- /src/pyflipper/lib/ir.py: -------------------------------------------------------------------------------- 1 | from .threaded import Threaded 2 | from .utils import is_hexstring 3 | 4 | class Ir(Threaded): 5 | # TODO: auto parse available protocols from >: ir help 6 | PROTOCOLS = ["NEC", "NEC42", "NEC42ext", "Samsung32", 7 | "RC6", "RC5", "RC5X", "SIRC", "SIRC15", "SIRC20"] 8 | 9 | def __init__(self, serial_wrapper) -> None: 10 | self._serial_wrapper = serial_wrapper 11 | 12 | def tx(self, protocol: str, hex_address: str, hex_command: str) -> None: 13 | assert protocol in self.PROTOCOLS, f"Available protocols: {self.PROTOCOLS}" 14 | assert is_hexstring(hex_address), "hex_address must be hexstring" 15 | assert is_hexstring(hex_command), "hex_command must be hexstring" 16 | address = ' '.join(hex_address[i:i+2] for i in range(0, len(hex_address), 2)) if " " not in hex_address else hex_address 17 | command = ' '.join(hex_command[i:i+2] for i in range(0, len(hex_command), 2)) if " " not in hex_command else hex_command 18 | self._serial_wrapper.send(f"ir tx {protocol} {address} {command}") 19 | 20 | def rx(self, timeout: int = 5) -> str: 21 | def _run() -> str: 22 | data = self._serial_wrapper.send("ir rx") 23 | # TODO parse data 24 | return data 25 | self.exec(func=None, timeout=timeout) 26 | return _run() 27 | 28 | def tx_raw(self, frequency:int , duty_cycle: float, samples: list or str) -> None: 29 | assert frequency > 10000 and frequency < 56000, "Frequency must be in range (10000 - 56000)" 30 | assert duty_cycle >= 0.0 and duty_cycle <= 1.0, "Duty cycle must be in range (0.0 - 1.0)" 31 | if isinstance(samples, list): 32 | samples = " ".join(list(map(lambda x: str(x), samples))) 33 | self._serial_wrapper.send(f"ir tx RAW F:{frequency} DC:{int(duty_cycle*100)} {samples}") 34 | 35 | -------------------------------------------------------------------------------- /src/pyflipper/lib/serial_wrapper.py: -------------------------------------------------------------------------------- 1 | 2 | import os 3 | import socket 4 | import serial 5 | import re 6 | from websocket import create_connection 7 | 8 | def error_handler(func): 9 | def handler(*args): 10 | result = func(*args) 11 | pattern = re.compile("\w+\serror:\s.*") 12 | match = pattern.match(result) 13 | if match: 14 | raise Exception(match.group()) 15 | return result 16 | return handler 17 | 18 | class LocalSerial: 19 | def __init__(self, com) -> None: 20 | self._serial_port = serial.Serial(port=com, baudrate=9600, 21 | bytesize=8, timeout=None, stopbits=serial.STOPBITS_ONE) 22 | self._serial_port.write(f"\n\n\r".encode()) 23 | self._serial_port.read_until(b'>:') #skip welcome banner 24 | 25 | @error_handler 26 | def send(self, payload: str) -> str: 27 | self._serial_port.write(f"{payload}\r".encode()) 28 | #time.sleep(0.5) 29 | self._serial_port.readline() 30 | return self._serial_port.read_until(b'>:').decode().rstrip('\r\n') 31 | 32 | def write(self, msg): 33 | self._serial_port.write(msg) 34 | 35 | def ctrl_c(self): 36 | self._serial_port.write(b'\x03') 37 | 38 | class WSSerial: 39 | def __init__(self, ws) -> None: 40 | self.ws = create_connection(ws) 41 | self.ws.recv() #skip welcome 42 | 43 | @error_handler 44 | def send(self, payload: str) -> str: 45 | self.ws.send_binary(f"{payload}\r".encode()) 46 | line = "" 47 | while ">:" not in line: 48 | line += self.ws.recv() 49 | return line.split(f'{payload}\r\n')[-1].rstrip('\r\n>: ') 50 | 51 | def write(self, msg): 52 | self.ws.send_binary(msg) 53 | 54 | def ctrl_c(self): 55 | self.ws.send_binary(b'\x03') 56 | 57 | class TcpSerial: 58 | def __init__(self, addr) -> None: 59 | host, _, port = addr.partition(':') 60 | port = int(port) 61 | self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 62 | self._socket.connect((host, port)) 63 | self._read_response() 64 | 65 | @error_handler 66 | def send(self, payload: str) -> str: 67 | self._socket.sendall(f"{payload}\r".encode()) 68 | return self._read_response() 69 | 70 | def write(self, msg): 71 | self._socket.sendall(msg) 72 | 73 | def ctrl_c(self): 74 | self._socket.sendall(b'\x03') 75 | 76 | def _read_response(self): 77 | buff = '' 78 | while True: 79 | buff += self._socket.recv(1024).decode() 80 | if '>:' in buff: 81 | break 82 | return '\n'.join(buff.splitlines()[1:-2]) 83 | -------------------------------------------------------------------------------- /src/pyflipper/pyflipper.py: -------------------------------------------------------------------------------- 1 | from .lib.bt import Bt 2 | from .lib.debug import Debug 3 | from .lib.device_info import DeviceInfo 4 | from .lib.free import Free 5 | from .lib.i2c import I2c 6 | from .lib.ikey import Ikey 7 | from .lib.input import Input 8 | from .lib.ir import Ir 9 | from .lib.led import Led 10 | from .lib.log import Log 11 | #from .lib.log import Log 12 | from .lib.music_player import MusicPlayer 13 | from .lib.nfc import NFC 14 | from .lib.onewire import Onewire 15 | from .lib.ps import Ps 16 | from .lib.rfid import RFID 17 | from .lib.serial_wrapper import LocalSerial, WSSerial, TcpSerial 18 | from .lib.storage import Storage 19 | from .lib.subghz import Subghz 20 | from .lib.vibro import Vibro 21 | from .lib.date import Date 22 | from .lib.gpio import Gpio 23 | from .lib.loader import Loader 24 | from .lib.power import Power 25 | from .lib.update import Update 26 | 27 | class PyFlipper: 28 | 29 | def __init__(self, **kwargs) -> None: 30 | assert sum(bool(kwargs.get(k)) for k in ('com', 'ws', 'tcp')) == 1, \ 31 | 'Only one of com, ws, tcp should be specified' 32 | if kwargs.get('com'): 33 | self._serial_wrapper = LocalSerial(com=kwargs['com']) 34 | elif kwargs.get('ws'): 35 | self._serial_wrapper = WSSerial(ws=kwargs['ws']) 36 | else: 37 | self._serial_wrapper = TcpSerial(addr=kwargs['tcp']) 38 | self.vibro = Vibro(serial_wrapper=self._serial_wrapper) 39 | self.date = Date(serial_wrapper=self._serial_wrapper) 40 | self.device_info = DeviceInfo(serial_wrapper=self._serial_wrapper) 41 | self.led = Led(serial_wrapper=self._serial_wrapper) 42 | self.bt = Bt(serial_wrapper=self._serial_wrapper) 43 | self.ps = Ps(serial_wrapper=self._serial_wrapper) 44 | self.free = Free(serial_wrapper=self._serial_wrapper) 45 | self.storage = Storage(serial_wrapper=self._serial_wrapper) 46 | self.gpio = Gpio(serial_wrapper=self._serial_wrapper) 47 | self.loader = Loader(serial_wrapper=self._serial_wrapper) 48 | self.music_player = MusicPlayer(serial_wrapper=self._serial_wrapper) 49 | self.power = Power(serial_wrapper=self._serial_wrapper) 50 | self.update = Update(serial_wrapper=self._serial_wrapper) 51 | self.log = Log(serial_wrapper=self._serial_wrapper) 52 | self.nfc = NFC(serial_wrapper=self._serial_wrapper) 53 | self.rfid = RFID(serial_wrapper=self._serial_wrapper) 54 | self.subghz = Subghz(serial_wrapper=self._serial_wrapper) 55 | self.ir = Ir(serial_wrapper=self._serial_wrapper) 56 | self.ikey = Ikey(serial_wrapper=self._serial_wrapper) 57 | self.debug = Debug(serial_wrapper=self._serial_wrapper) 58 | self.onewire = Onewire(serial_wrapper=self._serial_wrapper) 59 | self.i2c = I2c(serial_wrapper=self._serial_wrapper) 60 | self.input = Input(serial_wrapper=self._serial_wrapper) -------------------------------------------------------------------------------- /src/pyflipper/lib/storage.py: -------------------------------------------------------------------------------- 1 | import re 2 | import time 3 | 4 | from .threaded import Threaded 5 | 6 | 7 | # TODO: Leverage pathlib to validate and manage file paths 8 | 9 | 10 | class Storage: 11 | class Write(Threaded): 12 | def __init__(self, serial_wrapper) -> None: 13 | self._serial_wrapper = serial_wrapper 14 | 15 | def start(self, file: str) -> None: 16 | def _run(): 17 | self._serial_wrapper.send(f"storage write {file}") 18 | self.exec(func=_run, timeout=None) 19 | 20 | def send(self, text: str) -> None: 21 | if self.thread.is_alive(): 22 | #replace carriage return with ctrl+Enter 23 | self._serial_wrapper.write(text.replace('\r\n', '\x0d').encode()) 24 | time.sleep(0.5) 25 | 26 | def file(self, text: str, path: str) -> None: 27 | self.start(path) 28 | self.send(text) 29 | self.stop() 30 | 31 | def __init__(self, serial_wrapper) -> None: 32 | self._serial_wrapper = serial_wrapper 33 | self.write = __class__.Write(serial_wrapper=serial_wrapper) 34 | 35 | def info(self, fs: str) -> dict: 36 | assert fs in ('/ext', '/int'), "Storage filesystem must be '/ext' or '/int'" 37 | info_p = re.compile("(\w+):\s(.+)") 38 | response = self._serial_wrapper.send(f"storage info {fs}") 39 | info = info_p.findall(response) 40 | size_p = re.compile("(\d+)KB\s(\w+)") 41 | size = size_p.findall(response) 42 | return { info[0][0]: info[0][1].rstrip(), info[1][0]: info[1][1].rstrip(), size[0][1]+"_KB": int(size[0][0]), size[1][1]+"_KB": int(size[1][0])} 43 | 44 | def format(self): 45 | # TODO: implement 46 | pass 47 | 48 | def _explorer(self, cmd: str, path: str) -> dict: 49 | dirs_p = re.compile("\[D\]\s(\w+)") 50 | files_p = re.compile("\[F\]\s(.+)\s(\d+)(\w+)") 51 | response = self._serial_wrapper.send(f"storage {cmd} {path}") 52 | dirs = dirs_p.findall(response) 53 | files = [{'name': file[0], 'size': int(file[1]), 'weight': file[2]} for file in files_p.findall(response)] 54 | return {'dirs': dirs, 'files': files} 55 | 56 | def list(self, path: str) -> dict: 57 | return self._explorer("list", path) 58 | 59 | def tree(self, path: str) -> dict: 60 | return self._explorer("tree", path) 61 | 62 | def remove(self, file: str) -> None: 63 | self._serial_wrapper.send(f"storage remove {file}") 64 | 65 | def read(self, file: str) -> str: 66 | try: 67 | return self._serial_wrapper.send(f"storage read {file}").split('\r\n')[1] 68 | except IndexError: 69 | return "" 70 | 71 | def copy(self, src: str, dest: str) -> None: 72 | self._serial_wrapper.send(f"storage copy {src} {dest}") 73 | 74 | def rename(self, file: str, new_file: str) -> None: 75 | self._serial_wrapper.send(f"storage rename {file} {new_file}") 76 | 77 | def mkdir(self, new_dir: str) -> None: 78 | self._serial_wrapper.send(f"storage mkdir {new_dir}") 79 | 80 | def md5(self, file: str) -> str: 81 | return self._serial_wrapper.send(f"storage md5 {file}") 82 | 83 | def stat(self, file: str) -> str: 84 | return self._serial_wrapper.send(f"storage stat {file}") 85 | 86 | 87 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib64/ 18 | parts/ 19 | sdist/ 20 | var/ 21 | wheels/ 22 | share/python-wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .nox/ 42 | .coverage 43 | .coverage.* 44 | .cache 45 | nosetests.xml 46 | coverage.xml 47 | *.cover 48 | *.py,cover 49 | .hypothesis/ 50 | .pytest_cache/ 51 | cover/ 52 | 53 | # Translations 54 | *.mo 55 | *.pot 56 | 57 | # Django stuff: 58 | *.log 59 | local_settings.py 60 | db.sqlite3 61 | db.sqlite3-journal 62 | 63 | # Flask stuff: 64 | instance/ 65 | .webassets-cache 66 | 67 | # Scrapy stuff: 68 | .scrapy 69 | 70 | # Sphinx documentation 71 | docs/_build/ 72 | 73 | # PyBuilder 74 | .pybuilder/ 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | # For a library or package, you might want to ignore these files since the code is 86 | # intended to run in multiple environments; otherwise, check them in: 87 | # .python-version 88 | 89 | # pipenv 90 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 91 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 92 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 93 | # install all needed dependencies. 94 | #Pipfile.lock 95 | 96 | # poetry 97 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 98 | # This is especially recommended for binary packages to ensure reproducibility, and is more 99 | # commonly ignored for libraries. 100 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 101 | #poetry.lock 102 | 103 | # pdm 104 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 105 | #pdm.lock 106 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 107 | # in version control. 108 | # https://pdm.fming.dev/#use-with-ide 109 | .pdm.toml 110 | 111 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 112 | __pypackages__/ 113 | 114 | # Celery stuff 115 | celerybeat-schedule 116 | celerybeat.pid 117 | 118 | # SageMath parsed files 119 | *.sage.py 120 | 121 | # Environments 122 | .env 123 | .venv 124 | env/ 125 | venv/ 126 | ENV/ 127 | env.bak/ 128 | venv.bak/ 129 | 130 | # Spyder project settings 131 | .spyderproject 132 | .spyproject 133 | 134 | # Rope project settings 135 | .ropeproject 136 | 137 | # mkdocs documentation 138 | /site 139 | 140 | # mypy 141 | .mypy_cache/ 142 | .dmypy.json 143 | dmypy.json 144 | 145 | # Pyre type checker 146 | .pyre/ 147 | 148 | # pytype static type analyzer 149 | .pytype/ 150 | 151 | # Cython debug symbols 152 | cython_debug/ 153 | 154 | # PyCharm 155 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 156 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 157 | # and can be added to the global gitignore or merged into this file. For a more nuclear 158 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 159 | #.idea/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PyFlipper 2 | 3 | Flipper Zero Python CLI Wrapper 4 | 5 | ![](https://thumb.tildacdn.com/tild3332-3839-4061-b663-363464303432/-/resize/214x/-/format/webp/noroot.png) 6 | 7 | ## Articles 8 | - [How to hack a restaurant](https://medium.com/@nic_whr/how-to-hack-a-restaurant-5d394be105a9) 9 | 10 | ## Functions and characteristics: 11 | - [x] Flipper serial CLI wrapper 12 | - [x] Websocket client interface 13 | 14 | ## Setup instructions: 15 | 16 | ```bash 17 | $ pip install pyflipper 18 | ``` 19 | 20 | ### Tested on: 21 | - [x] Python 3.8.10 on Linux 5.4.0 x86_64 22 | - [x] Python 3.9.10 on Windows 10 23 | - [x] Python 3.10.5 on Android 12 (Termux + [OTGSerial2WebSocket](https://play.google.com/store/apps/details?id=com.wh00hw.serial2websocket) NO ROOT REQUIRED) 24 | 25 | ## Usage/Examples 26 | 27 | ### Connection 28 | 29 | ```python 30 | from pyflipper import PyFlipper 31 | 32 | # Local serial port 33 | flipper = PyFlipper(com="/dev/ttyACM0") 34 | 35 | # OR 36 | 37 | # Remote serial2websocket server 38 | flipper = PyFlipper(ws="ws://192.168.1.5:1337") 39 | 40 | # TCP 41 | flipper = PyFlipper(tcp='192.168.89.222:22170') 42 | ``` 43 | 44 | ### Power 45 | 46 | ```python 47 | # Info 48 | info = flipper.power.info() 49 | 50 | # Poweroff 51 | flipper.power.off() 52 | 53 | # Reboot 54 | flipper.power.reboot() 55 | 56 | # Reboot in DFU mode 57 | flipper.power.reboot2dfu() 58 | ``` 59 | 60 | ### Update/Backup 61 | 62 | ```python 63 | # Install update from .fuf file 64 | flipper.update.install(fuf_file="/ext/update.fuf") 65 | 66 | # Backup Flipper to .tar file 67 | flipper.update.backup(dest_tar_file="/ext/backup.tar") 68 | 69 | # Restore Flipper from backup .tar file 70 | flipper.update.restore(bak_tar_file="/ext/backup.tar") 71 | ``` 72 | 73 | ### Loader 74 | 75 | ```python 76 | # List installed apps 77 | apps = flipper.loader.list() 78 | 79 | # Open app 80 | flipper.loader.open(app_name="Clock") 81 | ``` 82 | 83 | ### Flipper Info 84 | 85 | ```python 86 | # Get flipper date 87 | date = flipper.date.date() 88 | 89 | # Get flipper timestamp 90 | timestamp = flipper.date.timestamp() 91 | 92 | # Get the processes dict list 93 | ps = flipper.ps.list() 94 | 95 | # Get device info dict 96 | device_info = flipper.device_info.info() 97 | 98 | # Get heap info dict 99 | heap = flipper.free.info() 100 | 101 | # Get free_blocks string 102 | free_blocks = flipper.free.blocks() 103 | 104 | # Get bluetooth info 105 | bt_info = flipper.bt.info() 106 | ``` 107 | 108 | ### Storage 109 | 110 | #### Filesystem Info 111 | 112 | ```python 113 | # Get the storage filesystem info 114 | ext_info = flipper.storage.info(fs="/ext") 115 | ``` 116 | 117 | #### Explorer 118 | 119 | ```python 120 | # Get the storage /ext dict 121 | ext_list = flipper.storage.list(path="/ext") 122 | 123 | # Get the storage /ext tree dict 124 | ext_tree = flipper.storage.tree(path="/ext") 125 | 126 | # Get file info 127 | file_info = flipper.storage.stat(file="/ext/foo/bar.txt") 128 | 129 | # Make directory 130 | flipper.storage.mkdir(new_dir="/ext/foo") 131 | ``` 132 | 133 | #### Files 134 | 135 | ```python 136 | # Read file 137 | plain_text = flipper.storage.read(file="/ext/foo/bar.txt") 138 | 139 | # Remove file 140 | flipper.storage.remove(file="/ext/foo/bar.txt") 141 | 142 | # Copy file 143 | flipper.storage.copy(src="/ext/foo/source.txt", dest="/ext/bar/destination.txt") 144 | 145 | # Rename file 146 | flipper.storage.rename(file="/ext/foo/bar.txt", new_file="/ext/foo/rab.txt") 147 | 148 | # MD5 Hash file 149 | md5_hash = flipper.storage.md5(file="/ext/foo/bar.txt") 150 | 151 | # Write file in one chunk 152 | file = "/ext/bar.txt" 153 | 154 | text = """There are many variations of passages of Lorem Ipsum available, 155 | but the majority have suffered alteration in some form, by injected humour, 156 | or randomised words which don't look even slightly believable. 157 | If you are going to use a passage of Lorem Ipsum, 158 | you need to be sure there isn't anything embarrassing hidden in the middle of text. 159 | """ 160 | 161 | flipper.storage.write.file(file, text) 162 | 163 | # Write file using a listener 164 | file = "/ext/foo.txt" 165 | 166 | text_one = """There are many variations of passages of Lorem Ipsum available, 167 | but the majority have suffered alteration in some form, by injected humour, 168 | or randomised words which don't look even slightly believable. 169 | If you are going to use a passage of Lorem Ipsum, 170 | you need to be sure there isn't anything embarrassing hidden in the middle of text. 171 | """ 172 | 173 | flipper.storage.write.start(file) 174 | 175 | time.sleep(2) 176 | 177 | flipper.storage.write.send(text_one) 178 | 179 | text_two = """All the Lorem Ipsum generators on the Internet tend to repeat predefined chunks as 180 | necessary, making this the first true generator on the Internet. 181 | It uses a dictionary of over 200 Latin words, combined with a handful of 182 | model sentence structures, to generate Lorem Ipsum which looks reasonable. 183 | The generated Lorem Ipsum is therefore always free from repetition, injected humour, or non-characteristic words etc. 184 | """ 185 | flipper.storage.write.send(text_two) 186 | 187 | time.sleep(3) 188 | 189 | # Don't forget to stop 190 | flipper.storage.write.stop() 191 | ``` 192 | 193 | ### LED/Backlight 194 | 195 | ```python 196 | # Set generic led on (r,b,g,bl) 197 | flipper.led.set(led='r', value=255) 198 | 199 | # Set blue led off 200 | flipper.led.blue(value=0) 201 | 202 | # Set green led value 203 | flipper.led.green(value=175) 204 | 205 | # Set backlight on 206 | flipper.led.backlight_on() 207 | 208 | # Set backlight off 209 | flipper.led.backlight_off() 210 | 211 | # Turn off led 212 | flipper.led.off() 213 | ``` 214 | 215 | ### Vibro 216 | 217 | ```python 218 | # Set vibro True or False 219 | flipper.vibro.set(True) 220 | 221 | # Set vibro on 222 | flipper.vibro.on() 223 | 224 | # Set vibro off 225 | flipper.vibro.off() 226 | ``` 227 | 228 | ### GPIO 229 | 230 | ```python 231 | # Set gpio mode: 0 - input, 1 - output 232 | flipper.gpio.mode(pin_name=PIN_NAME, value=1) 233 | 234 | # Set gpio pin value: 0 - off, 1 - on 235 | flipper.gpio.set(pin_name=PIN_NAME, value=1) 236 | 237 | # Read gpio pin value 238 | flipper.gpio.read(pin_name=PIN_NAME) 239 | ``` 240 | 241 | ### MusicPlayer 242 | 243 | ```python 244 | # Play song in RTTTL format 245 | 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" 246 | 247 | # Play in loop 248 | flipper.music_player.play(rtttl_code=rttl_song) 249 | 250 | # Stop loop 251 | flipper.music_player.stop() 252 | 253 | # Play for 20 seconds 254 | flipper.music_player.play(rtttl_code=rttl_song, duration=20) 255 | 256 | # Beep 257 | flipper.music_player.beep() 258 | 259 | # Beep for 5 seconds 260 | flipper.music_player.beep(duration=5) 261 | ``` 262 | 263 | ### NFC 264 | 265 | ```python 266 | # Synchronous default timeout 5 seconds 267 | 268 | # Detect NFC 269 | nfc_detected = flipper.nfc.detect() 270 | 271 | # Emulate NFC 272 | flipper.nfc.emulate() 273 | 274 | # Activate field 275 | flipper.nfc.field() 276 | ``` 277 | 278 | ### RFID 279 | 280 | ```python 281 | # Synchronous default timeout 5 seconds 282 | 283 | # Read RFID 284 | rfid = flipper.rfid.read() 285 | 286 | # Emulate RFID 287 | emulated = flipper.rfid.emulate(key_type="EM4100", key_data="5500824806") 288 | 289 | # Write RFID 290 | written = flipper.rfid.write(key_type="EM4100", key_data="5500824806") 291 | ``` 292 | 293 | ### SubGhz 294 | 295 | ```python 296 | # Transmit hex_key N times(default count = 10) 297 | flipper.subghz.tx(hex_key="DEADBEEF", frequency=433920000, count=5) 298 | 299 | # Receive (default frequency=433920000 raw=False timeout=5 seconds) 300 | received = flipper.subghz.rx(frequency="433920000", raw=True, timeout=10) 301 | 302 | # Replay recorded transmission 303 | flipper.subghz.tx_from_file("/ext/subghz/foo.sub") 304 | 305 | # Decode raw .sub file 306 | decoded = flipper.subghz.decode_raw(sub_file="/ext/subghz/foo.sub") 307 | ``` 308 | 309 | ### Infrared 310 | 311 | ```python 312 | # Transmit hex_address and hex_command selecting a protocol 313 | flipper.ir.tx(protocol="Samsung32", hex_address="C000FFEE", hex_command="DEADBEEF") 314 | 315 | # Raw Transmit samples 316 | flipper.ir.tx_raw(frequency=38000, duty_cycle=0.33, samples=[1337, 8888, 3000, 5555]) 317 | 318 | # Synchronous default timeout 5 seconds 319 | # Receive tx 320 | r = flipper.ir.rx(timeout=10) 321 | ``` 322 | 323 | ### IKEY 324 | 325 | ```python 326 | # Read (default timeout 5 seconds) 327 | ikey = flipper.ikey.read() 328 | 329 | # Write (default timeout 5 seconds) 330 | flipper.ikey.write(key_type="Dallas", key_data="DEADBEEFCOOOFFEE") 331 | 332 | # Emulate (default timeout 5 seconds) 333 | flipper.ikey.emulate(key_type="Dallas", key_data="DEADBEEFCOOOFFEE") 334 | ``` 335 | 336 | ### Log 337 | 338 | ```python 339 | # Attach event logger (default timeout 10 seconds) 340 | logs = flipper.log.attach() 341 | ``` 342 | 343 | ### Debug 344 | 345 | ```python 346 | # Activate debug mode 347 | flipper.debug.on() 348 | 349 | # Deactivate debug mode 350 | flipper.debug.off() 351 | ``` 352 | 353 | ### Onewire 354 | 355 | ```python 356 | # Search 357 | response = flipper.onewire.search() 358 | ``` 359 | 360 | ### I2C 361 | 362 | ```python 363 | # Get 364 | response = flipper.i2c.get() 365 | ``` 366 | 367 | ### Input 368 | 369 | ```python 370 | # Input dump 371 | dump = flipper.input.dump() 372 | 373 | # Send input 374 | flipper.input.send("up", "press") 375 | ``` 376 | 377 | ## Optimizations 378 | 379 | Feel free to contribute in any way 380 | 381 | - [ ] Queue Thread orchestrator 382 | - [ ] Implement all the cli functions 383 | - [ ] Async SubGhz Chat 384 | 385 | ## License 386 | 387 | [MIT](https://choosealicense.com/licenses/mit/) 388 | 389 | 390 | ## Buy me a pint 391 | 392 | **ZEC:** zs13zdde4mu5rj5yjm2kt6al5yxz2qjjjgxau9zaxs6np9ldxj65cepfyw55qvfp9v8cvd725f7tz7 393 | 394 | **ETH:** 0xef3cF1Eb85382EdEEE10A2df2b348866a35C6A54 395 | 396 | **BTC:** 15umRZXBzgUacwLVgpLPoa2gv7MyoTrKat 397 | 398 | ## Contacts 399 | 400 | - **Discord**: white_rabbit#4124 401 | - **Twitter**: @nic_whr 402 | - **GPG**: 0x94EDEADC 403 | --------------------------------------------------------------------------------