├── .gitattributes ├── .gitignore ├── .gitmodules ├── Makefile ├── README.md ├── doc.md ├── flipperzero_protobuf ├── README.md ├── __init__.py ├── cli_helpers.py ├── flipperCmd │ ├── __init__.py │ ├── __main__.py │ ├── cmd_complete.py │ ├── flipperCmd.py │ └── flipperzero_cmd.py ├── flipper_app.py ├── flipper_base.py ├── flipper_desktop.py ├── flipper_gpio.py ├── flipper_gui.py ├── flipper_property.py ├── flipper_proto.py ├── flipper_storage.py ├── flipper_sys.py ├── flipperzero_protobuf_compiled │ ├── __init__.py │ ├── __init__.pyi │ ├── application_pb2.py │ ├── desktop_pb2.py │ ├── flipper_pb2.py │ ├── gpio_pb2.py │ ├── gui_pb2.py │ ├── property_pb2.py │ ├── storage_pb2.py │ └── system_pb2.py ├── rebuild.sh └── version.py ├── pyproject.toml ├── requirements.txt ├── setup.py └── test_cmd.py /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | .DS_Store 3 | __pycache__/ 4 | *.py[cod] 5 | *$py.class 6 | 7 | .idea/ 8 | 9 | bin/ 10 | lib64 11 | pyvenv.cfg 12 | 13 | # C extensions 14 | *.so 15 | 16 | # Distribution / packaging 17 | .Python 18 | build/ 19 | develop-eggs/ 20 | dist/ 21 | downloads/ 22 | eggs/ 23 | .eggs/ 24 | lib/ 25 | lib64/ 26 | parts/ 27 | sdist/ 28 | var/ 29 | wheels/ 30 | share/python-wheels/ 31 | *.egg-info/ 32 | .installed.cfg 33 | *.egg 34 | MANIFEST 35 | 36 | # PyInstaller 37 | # Usually these files are written by a python script from a template 38 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 39 | *.manifest 40 | *.spec 41 | 42 | # Installer logs 43 | pip-log.txt 44 | pip-delete-this-directory.txt 45 | 46 | # Unit test / coverage reports 47 | htmlcov/ 48 | .tox/ 49 | .nox/ 50 | .coverage 51 | .coverage.* 52 | .cache 53 | nosetests.xml 54 | coverage.xml 55 | *.cover 56 | *.py,cover 57 | .hypothesis/ 58 | .pytest_cache/ 59 | cover/ 60 | 61 | # Translations 62 | *.mo 63 | *.pot 64 | 65 | # Django stuff: 66 | *.log 67 | local_settings.py 68 | db.sqlite3 69 | db.sqlite3-journal 70 | 71 | # Flask stuff: 72 | instance/ 73 | .webassets-cache 74 | 75 | # Scrapy stuff: 76 | .scrapy 77 | 78 | # Sphinx documentation 79 | docs/_build/ 80 | 81 | # PyBuilder 82 | .pybuilder/ 83 | target/ 84 | 85 | # Jupyter Notebook 86 | .ipynb_checkpoints 87 | 88 | # IPython 89 | profile_default/ 90 | ipython_config.py 91 | 92 | # pyenv 93 | # For a library or package, you might want to ignore these files since the code is 94 | # intended to run in multiple environments; otherwise, check them in: 95 | # .python-version 96 | 97 | # pipenv 98 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 99 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 100 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 101 | # install all needed dependencies. 102 | #Pipfile.lock 103 | 104 | # poetry 105 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 106 | # This is especially recommended for binary packages to ensure reproducibility, and is more 107 | # commonly ignored for libraries. 108 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 109 | #poetry.lock 110 | 111 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 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 maintainted 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/ 160 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "flipperzero-protobuf"] 2 | path = flipperzero_protobuf/flipperzero-protobuf 3 | url = https://github.com/flipperdevices/flipperzero-protobuf 4 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | 3 | # E201 whitespace after '[' 4 | # E202 whitespace before ']' 5 | # E302 expected 2 blank lines, found 1 6 | # E303 too many blank lines 7 | # E501 line too long 8 | # E741 ambiguous variable name 9 | 10 | # PEP8=pep8 11 | PYCODESTYLE=pycodestyle 12 | PEP8ARG=--ignore=E501,E128 --exclude=flipperzero_protobuf_compiled 13 | 14 | FILES=flipperzero_protobuf/*.py flipperzero_protobuf/flipperCmd/*.py 15 | 16 | all: 17 | @echo "Makefile targets: build clean pylint pip8" 18 | 19 | lint: pylint 20 | 21 | # Linting / Code Qual check 22 | # pylint --load-plugins perflint $$targ ; 23 | 24 | pylint: 25 | pylint flipperzero_protobuf 26 | 27 | pylint_each: 28 | for targ in ${FILES} ; do \ 29 | echo $$targ ; \ 30 | pylint $$targ ; \ 31 | done 32 | 33 | 34 | pep8: pycodestyle 35 | 36 | pycodestyle: 37 | ${PYCODESTYLE} ${PEP8ARG} flipperzero_protobuf 38 | 39 | 40 | clean: 41 | /bin/rm -fr dist __pycache__ build \ 42 | flipperzero_protobuf.egg-info \ 43 | flipperzero_protobuf/__pycache__ \ 44 | flipperzero_protobuf/flipperCmd/__pycache__ \ 45 | flipperzero_protobuf/flipperzero_protobuf_compiled/__pycache__ 46 | 47 | $(if $(wildcard run_local), /bin/bash run_local $@) 48 | 49 | build: 50 | python3 -m build 51 | $(if $(wildcard run_local), /bin/bash run_local $@) 52 | 53 | install: 54 | pip3 install . 55 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Python bindings for Flipper Zero protobuf protocol 2 | 3 | 4 | Python API binding/wrappers for Flipper Zero protobuf protocol and command line tool 5 | 6 | --- 7 | ### flipperCmd ### 8 | 9 | The command tool `flipperCmd` is terminal based tool for file transfer and remote command. 10 | It can be run from the command line or as an interactive app. 11 | 12 | It is still a work in progress (Alpha) but is functional. We are looking for help 13 | with stabilization of this build, making it work on any system. 14 | 15 | If you want to contribute, we run black and isort for code formatting purposes. 16 | 17 | --- 18 | 19 | 20 | ### Command Line Examples ### 21 | 22 | List and manage files from command line 23 | ``` 24 | $ flipperCmd ls 25 | Using port /dev/cu.usbmodemflip_Unyana1 26 | .fseventsd/ .Spotlight-V100/ badusb/ dolphin/ 27 | ibutton/ infrared/ lfrfid/ music_player/ 28 | nfc/ subghz/ u2f/ wav_player/ 29 | .metadata_never_index favorites.txt Manifest rwfiletest.bin 30 | 31 | ``` 32 | 33 | Copy single files to fromFlipper device 34 | ``` 35 | $ flipperCmd put My_Home_TV.ir /ext/infrared 36 | Using port /dev/cu.usbmodemflip_UOhBaby 37 | PUT My_Home_TV.ir /ext/infrared 38 | putting 206 bytes 39 | 40 | ``` 41 | 42 | Copy directory tree to Flipper device 43 | ``` 44 | $ flipperCmd put-tree subghz/samples /ext/subghz 45 | 46 | ``` 47 | 48 | #### Interactive Command Mode Examples #### 49 | 50 | 51 | ``` 52 | $ flipperCmd 53 | Using port /dev/cu.usbmodemflip_UOhBaby 54 | Entering interactive mode 55 | 56 | /ext flipper> help 57 | CAT : read flipper file to screen 58 | CD CHDIR : change current directory on flipper 59 | DEV-INFO : print device info 60 | DF INFO : get Filesystem info 61 | DU DISK-USAGE : display disk usage statistic 62 | GET GETFILE : copy file from flipper 63 | GET-TREE GETTREE : copy directory tree from flipper 64 | HELP ? : print command list 65 | HISTORY HIST : Print command history 66 | LCD LCHDIR : Change local current directory 67 | LPWD : print local working directory 68 | LS LIST : list files and dirs on Flipper device 69 | MD MKDIR : create a new directory 70 | MD5SUM MD5 : md5 hash of the file 71 | MV RENAME : rename file or dir 72 | PRINT-SCREEN : Take screendump in ascii or PBM format 73 | PUT PUTFILE : copy file to flipper 74 | PUT-TREE PUTTREE : copy directory tree to flipper 75 | QUIT EXIT : Exit Program 76 | REBOOT : reboot flipper 77 | RM DEL DELETE : delete file of directory on flipper device 78 | SEND SEND-COMMAND : Send non rpc command to flipper 79 | SET : set or print current option value 80 | STAT : get info about file or dir 81 | START-SESSION : (re) start RPC session 82 | STOP-SESSION : stop RPC session 83 | TIME : Set or Get Current time from Flipper 84 | ZIP : Generate Zip Archive 85 | 86 | ``` 87 | 88 | ``` 89 | /ext flipper> ls 90 | .Spotlight-V100/ .Trashes/ apps/ badusb/ 91 | dolphin/ elf/ ibutton/ infrared/ 92 | lfrfid/ music_player/ nfc/ subghz/ 93 | u2f/ wav_player/ .metadata_never_index favorites.txt 94 | Manifest rwfiletest.bin 95 | ``` 96 | 97 | ``` 98 | /ext flipper> ls ? 99 | Syntax : 100 | LS [-l] [-m] 101 | 102 | /ext flipper> ls -lm 103 | Storage List result: /ext 104 | .Spotlight-V100 DIR 105 | .Trashes DIR 106 | apps DIR 107 | badusb DIR 108 | dolphin DIR 109 | elf DIR 110 | ibutton DIR 111 | infrared DIR 112 | lfrfid DIR 113 | music_player DIR 114 | nfc DIR 115 | subghz DIR 116 | u2f DIR 117 | wav_player DIR 118 | .metadata_never_index 0 d41d8cd98f00b204e9800998ecf8427e 119 | favorites.txt 93 50c7a56f93d8f6c87f205691def774fa 120 | Manifest 16871 c74a84dea8d644198d27755313942614 121 | rwfiletest.bin 16384 3df67097cee5e4cea36e0f941c134ffc 122 | Total Bytes: 33348 123 | 124 | /ext flipper> rcd infrared/ 125 | remote directory = /ext/infrared 126 | 127 | /ext/infrared flipper> ls 128 | assets/ IRDB/ Sanyo/ TV_Philips/ 129 | Minolta.ir My_Home_TV.ir 130 | 131 | /ext/infrared flipper> quit 132 | Quit interactive mode 133 | ``` 134 | 135 | 136 | --- 137 | 138 | ### API Examples: ### 139 | ``` 140 | #!/usr/bin/env python3 141 | 142 | from flipperzero_protobuf.cli_helpers import print_hex, dict2datetime 143 | from flipperzero_protobuf.flipper_proto import FlipperProto 144 | 145 | 146 | def main(): 147 | 148 | proto = FlipperProto() 149 | 150 | print("\n\nPing") 151 | ping_rep = proto.rpc_system_ping() 152 | print_hex(ping_rep) 153 | 154 | print("\n\n]DeviceInfo") 155 | device_info = proto.rpc_device_info() 156 | print(device_info) 157 | 158 | print("\n\nGetDateTime") 159 | dtime_resp = proto.rpc_get_datetime() 160 | dt = dict2datetime(dtime_resp) 161 | print(dt.ctime()) 162 | 163 | print("\n\nList files") 164 | list_resp = proto.rpc_storage_list('/ext') 165 | for li in list_resp: 166 | print(f"[{li['type']}]\t{li['name']}") 167 | 168 | # run Infrared App 169 | print("\n\nrun Infrared App") 170 | proto.rpc_app_start('Infrared', '/ext/infrared/Tv_Tivo.ir') 171 | 172 | 173 | if __name__ == '__main__': 174 | main() 175 | 176 | ``` 177 | 178 | --- 179 | 180 | See Also:
181 | [flipperdevices/flipperzero-protobuf](http://github.com/flipperdevices/flipperzero-protobuf)
182 | [flipperdevices/go-flipper](https://github.com/flipperdevices/go-flipper) 183 | 184 | Special thanks: 185 | Evilpete - for making a wrapper and a code cleanup for our internal tool! -------------------------------------------------------------------------------- /doc.md: -------------------------------------------------------------------------------- 1 | Module flipperzero_protobuf 2 | =========================== 3 | 4 | Sub-modules 5 | ----------- 6 | * flipperzero_protobuf.cli_helpers 7 | * flipperzero_protobuf.flipperCmd 8 | * flipperzero_protobuf.flipper_app 9 | * flipperzero_protobuf.flipper_base 10 | * flipperzero_protobuf.flipper_gpio 11 | * flipperzero_protobuf.flipper_gui 12 | * flipperzero_protobuf.flipper_proto 13 | * flipperzero_protobuf.flipper_storage 14 | * flipperzero_protobuf.flipper_sys 15 | * flipperzero_protobuf.flipperzero_cmd 16 | 17 | Classes 18 | ------- 19 | 20 | `FlipperProto(serial_port=None, debug=0)` 21 | : Meta command class 22 | 23 | ### Ancestors (in MRO) 24 | 25 | * flipperzero_protobuf.flipper_base.FlipperProtoBase 26 | * flipperzero_protobuf.flipper_sys.FlipperProtoSys 27 | * flipperzero_protobuf.flipper_gpio.FlipperProtoGpio 28 | * flipperzero_protobuf.flipper_app.FlipperProtoApp 29 | * flipperzero_protobuf.flipper_gui.FlipperProtoGui 30 | * flipperzero_protobuf.flipper_storage.FlipperProtoStorage 31 | 32 | ### Methods 33 | 34 | `rpc_app_button_press(self, args) ‑> None` 35 | : Send button press command to app. 36 | 37 | Returns 38 | ---------- 39 | None 40 | 41 | Raises 42 | ---------- 43 | cmdException 44 | 45 | `rpc_app_button_release(self) ‑> None` 46 | : Send button release command to app 47 | 48 | Returns 49 | ---------- 50 | None 51 | 52 | Raises 53 | ---------- 54 | cmdException 55 | 56 | `rpc_app_exit(self) ‑> None` 57 | : Send exit command to app 58 | 59 | Returns 60 | ---------- 61 | None 62 | 63 | Raises 64 | ---------- 65 | cmdException 66 | 67 | `rpc_app_load_file(self, path) ‑> None` 68 | : Send load file command to app. 69 | 70 | Returns 71 | ---------- 72 | None 73 | 74 | Raises 75 | ---------- 76 | cmdException 77 | 78 | `rpc_app_start(self, name, args) ‑> None` 79 | : Start/Run application 80 | 81 | Parameters 82 | ---------- 83 | name : str 84 | args : str 85 | 86 | Returns 87 | ---------- 88 | None 89 | 90 | Raises 91 | ---------- 92 | cmdException 93 | 94 | `rpc_audiovisual_alert(self) ‑> None` 95 | : Launch audiovisual alert on flipper ?? 96 | 97 | Parameters 98 | ---------- 99 | None 100 | 101 | Returns 102 | ---------- 103 | None 104 | 105 | Raises 106 | ---------- 107 | cmdException 108 | 109 | `rpc_backup_create(self, archive_path=None) ‑> None` 110 | : Create Backup 111 | 112 | Parameters 113 | ---------- 114 | archive_path : str 115 | path to archive_path 116 | 117 | Returns 118 | ------- 119 | None 120 | 121 | Raises 122 | ---------- 123 | cmdException 124 | 125 | `rpc_backup_restore(self, archive_path=None) ‑> None` 126 | : Backup Restore 127 | 128 | Parameters 129 | ---------- 130 | archive_path : str 131 | path to archive_path 132 | 133 | Returns 134 | ------- 135 | None 136 | 137 | Raises 138 | ---------- 139 | cmdException 140 | 141 | `rpc_delete(self, path=None, recursive=False) ‑> None` 142 | : delete file or dir 143 | 144 | Parameters 145 | ---------- 146 | path : str 147 | path to file or dir on flipper device 148 | 149 | Raises 150 | ---------- 151 | cmdException 152 | 153 | `rpc_device_info(self) ‑> tuple[str, str]` 154 | : Device Info 155 | 156 | Return 157 | ---------- 158 | key, value : str 159 | 160 | Raises 161 | ---------- 162 | cmdException 163 | 164 | `rpc_factory_reset(self) ‑> None` 165 | : Factory Reset 166 | 167 | Parameters 168 | ---------- 169 | None 170 | 171 | Returns 172 | ---------- 173 | None 174 | 175 | Raises 176 | ---------- 177 | cmdException 178 | 179 | `rpc_get_datetime(self) ‑> dict` 180 | : Get system Date and Time 181 | 182 | Parameters 183 | ---------- 184 | None 185 | 186 | Returns 187 | ---------- 188 | dict 189 | keys: 'year', 'month', 'day', 'hour', 'minute', 'second', 'weekday' 190 | 191 | Raises 192 | ---------- 193 | cmdException 194 | 195 | `rpc_gpio_get_pin_mode(self, pin) ‑> str` 196 | : get GPIO pin mode 197 | 198 | Parameters 199 | ---------- 200 | pin : int or str 201 | 0 = 'PC0' 202 | 1 = 'PC1' 203 | 2 = 'PC3' 204 | 3 = 'PB2' 205 | 4 = 'PB3' 206 | 5 = 'PA4' 207 | 6 = 'PA6' 208 | 7 = 'PA7' 209 | 210 | Returns: 211 | ---------- 212 | str 213 | 'OUTPUT' 214 | 'INPUT' 215 | 216 | Raises 217 | ---------- 218 | InputTypeException 219 | cmdException 220 | 221 | `rpc_gpio_read_pin(self, pin) ‑> int` 222 | : query GPIO pin 223 | 224 | Parameters 225 | ---------- 226 | pin : int or str 227 | 0 = 'PC0' 228 | 1 = 'PC1' 229 | 2 = 'PC3' 230 | 3 = 'PB2' 231 | 4 = 'PB3' 232 | 5 = 'PA4' 233 | 6 = 'PA6' 234 | 7 = 'PA7' 235 | 236 | Returns: 237 | ---------- 238 | int 239 | pin value 240 | 241 | Raises 242 | ---------- 243 | InputTypeException 244 | cmdException 245 | 246 | `rpc_gpio_set_input_pull(self, pin, pull_mode) ‑> None` 247 | : Set GPIO pill Input 248 | 249 | Parameters 250 | ---------- 251 | pin : int or str 252 | 0 = 'PC0' 253 | 1 = 'PC1' 254 | 2 = 'PC3' 255 | 3 = 'PB2' 256 | 4 = 'PB3' 257 | 5 = 'PA4' 258 | 6 = 'PA6' 259 | 7 = 'PA7' 260 | 261 | pull_mode : str 262 | 0 = 'NO' 263 | 1 = 'UP' 264 | 2 = 'DOWN' 265 | 266 | Returns: 267 | ---------- 268 | None 269 | 270 | Raises 271 | ---------- 272 | InputTypeException 273 | cmdException 274 | 275 | `rpc_gpio_set_pin_mode(self, pin, mode) ‑> None` 276 | : set GPIO pin mode 277 | 278 | Parameters 279 | ---------- 280 | pin : int or str 281 | 0 = 'PC0' 282 | 1 = 'PC1' 283 | 2 = 'PC3' 284 | 3 = 'PB2' 285 | 4 = 'PB3' 286 | 5 = 'PA4' 287 | 6 = 'PA6' 288 | 7 = 'PA7' 289 | 290 | mode : str 291 | 0 = 'OUTPUT' 292 | 1 = 'INPUT' 293 | 294 | Returns: 295 | ---------- 296 | None 297 | 298 | Raises 299 | ---------- 300 | InputTypeException 301 | cmdException 302 | 303 | `rpc_gpio_write_pin(self, pin, value) ‑> None` 304 | : write GPIO pin 305 | 306 | Parameters 307 | ---------- 308 | pin : int or str 309 | 0 = 'PC0' 310 | 1 = 'PC1' 311 | 2 = 'PC3' 312 | 3 = 'PB2' 313 | 4 = 'PB3' 314 | 5 = 'PA4' 315 | 6 = 'PA6' 316 | 7 = 'PA7' 317 | 318 | value : int 319 | 320 | Returns: 321 | ---------- 322 | None 323 | 324 | Raises 325 | ---------- 326 | InputTypeException 327 | cmdException 328 | 329 | `rpc_gui_send_input(self, key_arg) ‑> None` 330 | : Send Input Event Request Type 331 | 332 | Parameters 333 | ---------- 334 | key_arg : tuple 335 | tuple = (InputKey, InputType) 336 | valid InputKeykey values: 'UP', 'DOWN', 'RIGHT', 'LEFT', 'OK' 337 | valid InputType values: 'PRESS', 'RELEASE', 'SHORT', 'LONG', 'REPEAT' 338 | 339 | Returns 340 | ------- 341 | None 342 | 343 | Raises 344 | ---------- 345 | cmdException 346 | InputTypeException 347 | 348 | `rpc_gui_send_input_event_request(self, key, itype) ‑> None` 349 | : Send Input Event Request Key 350 | 351 | Parameters 352 | ---------- 353 | key : str 354 | 'UP', 'DOWN', 'RIGHT', 'LEFT', 'OK' 355 | itype : str 356 | 'PRESS', 'RELEASE', 'SHORT', 'LONG', 'REPEAT' 357 | 358 | Returns 359 | ------- 360 | None 361 | 362 | Raises 363 | ---------- 364 | cmdException 365 | 366 | `rpc_gui_snapshot_screen(self) ‑> bytes` 367 | : Snapshot screen 368 | 369 | Parameters 370 | ---------- 371 | None 372 | 373 | Returns 374 | ------- 375 | bytes 376 | 377 | Raises 378 | ---------- 379 | cmdException 380 | 381 | `rpc_gui_start_screen_stream(self) ‑> None` 382 | : Start screen stream 383 | 384 | Parameters 385 | ---------- 386 | None 387 | 388 | Returns 389 | ------- 390 | None 391 | 392 | Raises 393 | ---------- 394 | cmdException 395 | 396 | `rpc_info(self, path=None) ‑> dict` 397 | : get filesystem info 398 | 399 | Parameters 400 | ---------- 401 | path : str 402 | path to filesystem 403 | 404 | Returns: 405 | ---------- 406 | dict 407 | 408 | Raises 409 | ---------- 410 | cmdException 411 | 412 | `rpc_lock_status(self) ‑> bool` 413 | : Get LockScreen Status 414 | 415 | Returns 416 | ---------- 417 | bool 418 | 419 | Raises 420 | ---------- 421 | cmdException 422 | 423 | `rpc_md5sum(self, path=None) ‑> str` 424 | : get md5 of file 425 | 426 | Parameters 427 | ---------- 428 | path : str 429 | path to file on flipper device 430 | 431 | Raises 432 | ---------- 433 | cmdException 434 | 435 | `rpc_mkdir(self, path) ‑> None` 436 | : creates a new directory 437 | 438 | Parameters 439 | ---------- 440 | path : str 441 | path for ew directory on flipper device 442 | 443 | Raises 444 | ---------- 445 | cmdException 446 | 447 | `rpc_power_info(self) ‑> tuple[str, str]` 448 | : Power info / charging status 449 | 450 | Parameters 451 | ---------- 452 | None 453 | 454 | Returns 455 | ---------- 456 | key, value : str 457 | 458 | Raises 459 | ---------- 460 | cmdException 461 | 462 | `rpc_protobuf_version(self) ‑> tuple[int, int]` 463 | : Protobuf Version 464 | 465 | Parameters 466 | ---------- 467 | None 468 | 469 | Return 470 | ---------- 471 | major, minor : int 472 | 473 | Raises 474 | ---------- 475 | cmdException 476 | 477 | `rpc_read(self, path=None) ‑> bytes` 478 | : read file from flipperzero device 479 | 480 | Parameters 481 | ---------- 482 | path : str 483 | path to file on flipper device 484 | 485 | Returns 486 | ------- 487 | bytes 488 | 489 | Raises 490 | ---------- 491 | cmdException 492 | 493 | `rpc_reboot(self, mode=0) ‑> None` 494 | : Reboot flipper 495 | 496 | Parameters 497 | ---------- 498 | mode : int or str 499 | 0 = OS 500 | 1 = DFU 501 | 2 = UPDATE 502 | 503 | Returns 504 | ---------- 505 | None 506 | 507 | Raises 508 | ---------- 509 | InputTypeException 510 | cmdException 511 | 512 | `rpc_rename_file(self, old_path=None, new_path=None) ‑> None` 513 | : rename file or dir 514 | 515 | Parameters 516 | ---------- 517 | old_path : str 518 | path to file or dir on flipper device 519 | new_path : str 520 | path to file or dir on flipper device 521 | 522 | Raises 523 | ---------- 524 | cmdException 525 | 526 | `rpc_set_datetime(self, arg_datetm=None) ‑> None` 527 | : Set system Date and Time 528 | 529 | Parameters 530 | ---------- 531 | datetm : dict or datetime obj 532 | dict keys: 'year', 'month', 'day', 'hour', 'minute', 'second', 'weekday' 533 | datetime obj 534 | None (default) method datetime.datetime.now() is called 535 | 536 | Returns 537 | ---------- 538 | None 539 | 540 | Raises 541 | ---------- 542 | InputTypeException 543 | cmdException 544 | 545 | `rpc_start_virtual_display(self, data) ‑> None` 546 | : Start Virtual Display 547 | 548 | Parameters 549 | ---------- 550 | data : bytes 551 | 552 | Returns 553 | ------- 554 | None 555 | 556 | Raises 557 | ---------- 558 | cmdException 559 | 560 | `rpc_stat(self, path=None) ‑> dict` 561 | : get info or file or directory file from flipperzero device 562 | 563 | Parameters 564 | ---------- 565 | path : str 566 | path to file on flipper device 567 | 568 | Raises 569 | ---------- 570 | cmdException 571 | 572 | `rpc_stop_session(self) ‑> None` 573 | : Stop RPC session 574 | 575 | Parameters 576 | ---------- 577 | None 578 | 579 | Returns 580 | ---------- 581 | None 582 | 583 | Raises 584 | ---------- 585 | cmdException 586 | 587 | `rpc_stop_virtual_display(self) ‑> None` 588 | : Stop Virtual Display 589 | 590 | Parameters 591 | ---------- 592 | None 593 | 594 | Returns 595 | ------- 596 | None 597 | 598 | Raises 599 | ---------- 600 | cmdException 601 | 602 | `rpc_storage_list(self, path='/ext') ‑> list` 603 | : get file & dir listing 604 | 605 | Parameters 606 | ---------- 607 | path : str 608 | path to filesystem 609 | 610 | Returns: 611 | ---------- 612 | list 613 | 614 | Raises 615 | ---------- 616 | cmdException 617 | 618 | `rpc_system_ping(self, data=b'\xde\xad\xbe\xef') ‑> list` 619 | : Ping flipper 620 | 621 | Parameters 622 | ---------- 623 | data : bytes 624 | 625 | Returns 626 | ---------- 627 | list 628 | 629 | Raises 630 | ---------- 631 | InputTypeException 632 | cmdException 633 | 634 | `rpc_update(self, update_manifest='') ‑> None` 635 | : Update 636 | 637 | Parameters 638 | ---------- 639 | update_manifest : str 640 | 641 | Returns 642 | ---------- 643 | None 644 | 645 | code ; str 646 | 0 OK 647 | 1 ManifestPathInvalid 648 | 2 ManifestFolderNotFound 649 | 3 ManifestInvalid 650 | 4 StageMissing 651 | 5 StageIntegrityError 652 | 6 ManifestPointerError 653 | 7 TargetMismatch 654 | 8 OutdatedManifestVersion 655 | 9 IntFull 656 | 657 | `rpc_write(self, path=None, data='') ‑> None` 658 | : write file from flipperzero device 659 | 660 | Parameters 661 | ---------- 662 | path : str 663 | path to file on flipper device 664 | data : bytes 665 | data to write 666 | 667 | Raises 668 | ---------- 669 | cmdException 670 | 671 | `send_cmd(self, cmd_str) ‑> None` 672 | : send non rpc command to flipper 673 | 674 | `start_rpc_session(self) ‑> None` 675 | : start rpc session 676 | -------------------------------------------------------------------------------- /flipperzero_protobuf/README.md: -------------------------------------------------------------------------------- 1 | ## class FlipperProto ## 2 | 3 | ### [Application Calls](flipper_app.py) ### 4 | --- 5 | 6 | rpc_lock_status() 7 | > Get LockScreen Status 8 | 9 | Returns: 10 | bool 11 | 12 | rpc_app_start(name, args) 13 | > Start/Run application 14 | 15 | rpc_app_exit() 16 | > Send exit command to app 17 | 18 | rpc_app_load_file(path) 19 | > Send load file command to app 20 | 21 | rpc_app_button_press(args) 22 | > Send button press command to app 23 | 24 | rpc_app_button_release() 25 | > Send button release command to app 26 | 27 | rpc_app_get_error() 28 | > Get extended error info 29 | 30 | Returns: 31 | Error info as tuple (int, str) 32 | 33 | rpc_app_data_exchange_send(data) 34 | > Send user data to application 35 | 36 | Arg: 37 | data: User data to send as bytes 38 | 39 | rpc_app_data_exchange_recv() 40 | > Receive user data from application 41 | 42 | Returns: 43 | Received user data as bytes 44 | 45 | 46 | ### [GPIO Calls](flipper_gui.py) ### 47 | --- 48 | 49 | rpc_gpio_get_pin_mode(pin) 50 | > get GPIO pin mode 51 | 52 | Arg: 53 | pin: 'PC0', 'PC1', 'PC3', 'PB2', 'PB3', 'PA4', 'PA6', 'PA7' 54 | 55 | rpc_gpio_set_pin_mode(pin, mode) 56 | > set GPIO pin mode 57 | 58 | Arg: 59 | pin: 'PC0', 'PC1', 'PC3', 'PB2', 'PB3', 'PA4', 'PA6', 'PA7' 60 | mode: 'OUTPUT', 'INPUT' 61 | 62 | rpc_gpio_write_pin(pin, value) 63 | > write GPIO pin 64 | 65 | Arg: 66 | pin: 'PC0', 'PC1', 'PC3', 'PB2', 'PB3', 'PA4', 'PA6', 'PA7' 67 | mode: bool 68 | 69 | rpc_gpio_read_pin(pin) 70 | > query GPIO pin 71 | 72 | Arg: 73 | pin: 'PC0', 'PC1', 'PC3', 'PB2', 'PB3', 'PA4', 'PA6', 'PA7' 74 | Returns: 75 | bool 76 | 77 | rpc_gpio_set_input_pull(pin, pull_mode) 78 | > Set GPIO pill Input 79 | 80 | Arg: 81 | pin: 'PC0', 'PC1', 'PC3', 'PB2', 'PB3', 'PA4', 'PA6', 'PA7' 82 | pull_mode : 'NO', 'UP', 'DOWN' 83 | 84 | 85 | ### [GUI Calls](flipper_gpio.py) ### 86 | --- 87 | 88 | rpc_start_virtual_display(data) 89 | > Start Virtual Display 90 | 91 | rpc_stop_virtual_display() 92 | > Stop Virtual Display 93 | 94 | rpc_gui_start_screen_stream() 95 | > Start screen stream 96 | 97 | rpc_gui_snapshot_screen() 98 | > Snapshot screen 99 | 100 | Returns: 101 | screen data as bytes 102 | 103 | 104 | rpc_gui_send_input_event_request(key, itype) 105 | > Send Input Event Request Key 106 | 107 | Arg: 108 | key : 'UP', 'DOWN', 'RIGHT', 'LEFT', 'OK' 109 | itype : 'PRESS', 'RELEASE', 'SHORT', 'LONG', 'REPEAT' 110 | 111 | 112 | rpc_gui_send_input(key_arg) 113 | > Send Input Event Request Type 114 | 115 | Arg: tuple (InputKey, InputType) 116 | InputKeykey values: 'UP', 'DOWN', 'RIGHT', 'LEFT', 'OK' 117 | InputType values: 'PRESS', 'RELEASE', 'SHORT', 'LONG', 'REPEAT' 118 | 119 | 120 | ### [Storage Calls](flipper_storage.py) ### 121 | --- 122 | 123 | rpc_backup_create(archive_path) 124 | > Create Backup 125 | 126 | rpc_backup_restore(archive_path) 127 | > Backup Restore 128 | 129 | rpc_read(path) 130 | > read file from flipperzero device 131 | 132 | Arg: 133 | path to file 134 | Returns: 135 | file data in bytes 136 | 137 | rpc_write(path, data) 138 | > write file from flipperzero device 139 | 140 | Arg: 141 | path to file 142 | data bytes to write 143 | 144 | rpc_info(path) 145 | > get filesystem info 146 | 147 | Arg: 148 | path to file 149 | Returns: 150 | dict containing 'totalSpace' 'freeSpace' 151 | 152 | rpc_stat(path) 153 | > get info or file or directory file from flipperzero device 154 | 155 | Arg: 156 | path to file 157 | Returns 158 | dict containing: 'name' 'type' 'size' 159 | 160 | 161 | rpc_md5sum(path) 162 | > get md5 of file 163 | 164 | Arg: 165 | path to file 166 | Returns 167 | md5 checksum of file 168 | 169 | rpc_mkdir(path) 170 | > creates a new directory 171 | 172 | Arg: 173 | path for new directory 174 | 175 | rpc_delete(path, recursive) 176 | > delete file or dir 177 | 178 | Arg: 179 | path to deleted file or directory 180 | 181 | rpc_rename_file(old_path, new_path) 182 | > rename file or dir 183 | 184 | Arg: 185 | old_path to file 186 | new_path to file 187 | 188 | rpc_storage_list(path") 189 | > get file & dir listing 190 | 191 | Arg: 192 | path to file 193 | Returns 194 | list of dict containing: 'name' 'type' 'size' 195 | 196 | ### [System Calls](flipper_sys.py) ### 197 | --- 198 | 199 | rpc_factory_reset() 200 | > Factory Reset 201 | 202 | rpc_update(update_manifest) 203 | > Update 204 | 205 | rpc_reboot(mode) 206 | > Reboot flipper 207 | 208 | Arg: 209 | mode: 'OS', 'DFU', 'UPDATE' 210 | 211 | rpc_power_info() 212 | > Power info / charging status 213 | 214 | Returns 215 | dict containing: 'key' & 'value' 216 | 217 | rpc_device_info() 218 | > Device Info 219 | 220 | Returns 221 | dict containing: 'key' & 'value' 222 | 223 | rpc_protobuf_version() 224 | > Protobuf Version 225 | 226 | Returns 227 | tuple: (major, minor) 228 | 229 | rpc_get_datetime() 230 | > Get system Date and Time 231 | 232 | Returns 233 | dict containing: 'year', 'month', 'day', 'hour', 'minute', 'second', 'weekday' 234 | 235 | rpc_set_datetime(arg_datetm) 236 | > Set system Date and Time 237 | 238 | Arg: 239 | datetime obj (optional) 240 | default = datetime.datetime.now() 241 | 242 | rpc_system_ping(data_bytes) 243 | > Ping flipper 244 | 245 | rpc_audiovisual_alert() 246 | > Launch audiovisual alert on flipper ?? 247 | 248 | rpc_stop_session() 249 | > Stop RPC session 250 | 251 | ### [Property Calls](flipper_property.py) ### 252 | --- 253 | 254 | rpc_property_get(key) 255 | > Get a property by key 256 | 257 | Arg: 258 | key: string with partially or fully specified key 259 | Returns: 260 | list of tuple (str, str) key-value pairs 261 | 262 | 263 | ### [Exceptions](flipper_base.py) ### 264 | --- 265 | 266 | FlipperProtoException
267 |       RPC Proto return code error 268 | 269 | 270 | InputTypeException
271 |       Input Type call argument error 272 | 273 | Varint32Exception
274 |       Protocal error, reading varint from serial port 275 | 276 | 277 | ----- 278 | 279 | See Also: [flipperdevices/flipperzero-protobuf](http://github.com/flipperdevices/flipperzero-protobuf) 280 | 281 | -------------------------------------------------------------------------------- /flipperzero_protobuf/__init__.py: -------------------------------------------------------------------------------- 1 | """FlipperProto Class Init""" 2 | 3 | from .flipper_proto import FlipperProto 4 | 5 | # from .cli_helpers import * 6 | 7 | __all__ = ["FlipperProto"] 8 | 9 | __pdoc__ = { 10 | "flipperzero_protobuf_compiled": False, 11 | "flipperzero_protobuf.flipperCmd": False, 12 | } 13 | -------------------------------------------------------------------------------- /flipperzero_protobuf/cli_helpers.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """command helper funtions FlipperProto Class""" 3 | 4 | import datetime 5 | import hashlib 6 | from collections.abc import Iterator 7 | 8 | import numpy 9 | 10 | from .flipper_base import InputTypeException 11 | 12 | __ALL__ = [ 13 | "print_hex", 14 | "calc_file_md5", 15 | "flipper_tree_walk", 16 | "datetime2dict", 17 | "dict2datetime", 18 | "calc_n_print_du", 19 | ] 20 | 21 | 22 | def print_hex(bytes_data) -> None: 23 | """print bytes in hex""" 24 | print("".join(f"{x:02x} " for x in bytes_data)) 25 | 26 | 27 | _SCREEN_H = 128 28 | _SCREEN_W = 64 29 | 30 | 31 | def calc_file_md5(fname) -> str: 32 | """Calculate md5 hash for file 33 | 34 | Parameters 35 | ---------- 36 | fname : str 37 | path to local fole 38 | 39 | Returns 40 | ------- 41 | str containing md5 message-digest fingerprint for file 42 | 43 | """ 44 | with open(fname, "rb") as fd: 45 | hsum = hashlib.md5(fd.read()).hexdigest() 46 | 47 | return hsum 48 | 49 | 50 | def _write_screen(dat) -> None: 51 | """write image data to terminal screen""" 52 | for y in range(0, _SCREEN_W, 2): 53 | for x in range(1, _SCREEN_H + 1): 54 | if int(dat[x][y]) == 1 and int(dat[x][y + 1]) == 1: 55 | print("\u2588", end="") 56 | if int(dat[x][y]) == 0 and int(dat[x][y + 1]) == 1: 57 | print("\u2584", end="") 58 | if int(dat[x][y]) == 1 and int(dat[x][y + 1]) == 0: 59 | print("\u2580", end="") 60 | if int(dat[x][y]) == 0 and int(dat[x][y + 1]) == 0: 61 | print(" ", end="") 62 | print() 63 | 64 | 65 | def _write_pbm_file(dat, dest) -> None: 66 | """write Black & White bitmap in simple Netpbm format""" 67 | with open(dest, "w", encoding="utf-8") as fd: 68 | print(f"P1\n{_SCREEN_H + 1} {_SCREEN_W}", file=fd) 69 | for y in range(0, _SCREEN_W): 70 | print(numpy.array2string(dat[:, y], max_line_width=300)[1:-1], file=fd) 71 | 72 | 73 | def _write_ppm_file(dat, dest) -> None: 74 | """write Orange and Black color RGB image stored in PPM format""" 75 | with open(dest, "w", encoding="utf-8") as fd: 76 | print(f"P3\n{_SCREEN_H + 1} {_SCREEN_W}\n255", file=fd) 77 | for y in range(0, _SCREEN_W): 78 | print( 79 | " ".join( 80 | ["255 165 000" if c == "1" else "000 000 000" for c in dat[:, y]] 81 | ) 82 | ) 83 | 84 | 85 | def print_screen(screen_bytes, dest=None) -> None: 86 | """convert screendump data into ascii or .pbm for format 87 | 88 | Parameters 89 | ---------- 90 | screen_bytes: numpy array 91 | dest : str 92 | filename (optional) 93 | 94 | Returns 95 | ------- 96 | None 97 | prints screen data as ascii 98 | If dest filename is given screen data is writen as pbm image 99 | 100 | Raises 101 | ---------- 102 | InputTypeException 103 | 104 | """ 105 | 106 | dat = _dump_screen(screen_bytes) 107 | 108 | if dest is None: # maybe also .txt files ? 109 | _write_screen(dat) 110 | return 111 | 112 | if dest.endswith(".pbm"): # Black & White bitmap in simple Netpbm format 113 | _write_pbm_file(dat, dest) 114 | return 115 | 116 | if dest.endswith(".ppm"): # Orange and Black color RGB image stored in PPM format 117 | _write_ppm_file(dat, dest) 118 | return 119 | 120 | raise InputTypeException("invalid filename: {dest}") 121 | 122 | 123 | def _dump_screen(screen_bytes) -> numpy.ndarray: 124 | """process` screendump data 125 | 126 | Parameters 127 | ---------- 128 | bytes: screen_bytes 129 | 130 | Returns 131 | ------- 132 | numpy array 133 | 134 | """ 135 | 136 | # pylint: disable=multiple-statements 137 | 138 | def get_bin(x) -> str: 139 | return format(x, "08b") 140 | 141 | scr = numpy.zeros((_SCREEN_H + 1, _SCREEN_W + 1), dtype=int) 142 | data = screen_bytes 143 | 144 | x = y = 0 145 | basey = 0 146 | 147 | for i in range(0, int(_SCREEN_H * _SCREEN_W / 8)): 148 | tmp = get_bin(data[i])[::-1] 149 | 150 | y = basey 151 | x += 1 152 | for c in tmp: 153 | scr[x][y] = c 154 | y += 1 155 | 156 | if (i + 1) % _SCREEN_H == 0: 157 | basey += 8 158 | x = 0 159 | 160 | return scr 161 | 162 | 163 | def flipper_tree_walk(dpath, proto, filedata=False) -> Iterator[str, list, list]: 164 | """Directory tree generator for flipper 165 | writen to have simular call interface as os.walk() 166 | 167 | Parameters 168 | ---------- 169 | dpath: str 170 | path to top of directory tree to follow 171 | proto : FlipperProto obj 172 | 173 | Returns 174 | ------- 175 | yields a tuple containing: 176 | dirpath: str 177 | path to current directory in tree 178 | dirnamess: list 179 | list of subdirectories in dirpath 180 | filenames: list 181 | list of filenames in dirpath 182 | 183 | """ 184 | list_resp = proto.rpc_storage_list(dpath) 185 | dlist = [] 186 | flist = [] 187 | 188 | for li in list_resp: 189 | if li["type"] == "DIR": 190 | dlist.append(li["name"]) 191 | else: 192 | if filedata: 193 | flist.append(li) 194 | else: 195 | flist.append(li["name"]) 196 | 197 | # print(dlist) 198 | yield dpath, dlist, flist 199 | for d in dlist: 200 | yield from flipper_tree_walk(dpath + "/" + d, proto, filedata=filedata) 201 | 202 | 203 | def datetime2dict(dt=None) -> dict: 204 | """Convert datetime obj into type dict 205 | 206 | Parameters 207 | ---------- 208 | dt : datetime obj 209 | 210 | Returns 211 | ------- 212 | dict type containing 'year' 'month' 'day' 'hour' 'minute' 'second' 'weekday' 213 | 214 | 215 | """ 216 | 217 | if dt is None: 218 | dt = datetime.datetime.now() 219 | 220 | tlist = list(dt.timetuple()) 221 | 222 | datetime_dict = { 223 | "year": tlist[0], 224 | "month": tlist[2], 225 | "day": tlist[3], 226 | "hour": tlist[4], 227 | "minute": tlist[5], 228 | "second": tlist[6], 229 | "weekday": tlist[7] + 1, 230 | } 231 | 232 | return datetime_dict 233 | 234 | 235 | def dict2datetime(d) -> datetime.datetime: 236 | """Convert type dict into datetime obj 237 | 238 | Parameters 239 | ---------- 240 | d : dicy 241 | dict type containing 'year' 'month' 'day' 'hour' 'minute' 'second' 'weekday' 242 | 243 | Returns 244 | ------- 245 | datetime obj 246 | 247 | Raises 248 | ---------- 249 | TypeError 250 | 251 | """ 252 | 253 | tdict = d.copy() # we dont want to destroy the caller's data 254 | del tdict["weekday"] 255 | return datetime.datetime(**tdict) 256 | 257 | 258 | def _get_dir_size(flip, dir_path) -> int: 259 | """calculate disk usage statistics for flipper folder and sub folders.""" 260 | total = 0 261 | for ROOT, DIRS, FILES in flipper_tree_walk(dir_path, flip, filedata=True): 262 | for d in DIRS: 263 | total += _get_dir_size(flip, f"{ROOT}/{d}") 264 | for f in FILES: 265 | total += f["size"] 266 | 267 | return total 268 | 269 | 270 | def calc_n_print_du(flip, dir_path) -> None: 271 | """prints folder disk usage statistics.""" 272 | 273 | if len(dir_path) > 1: 274 | dir_path = dir_path.rstrip("/") 275 | 276 | flist = flip.rpc_storage_list(dir_path) 277 | 278 | flist.sort(key=lambda x: (x["type"], x["name"].lower())) 279 | 280 | flist = flip.rpc_storage_list(dir_path) 281 | 282 | total_size = 0 283 | for line in flist: 284 | if line["type"] == "DIR": 285 | dsize = _get_dir_size(flip, f"{dir_path}/{line['name']}") 286 | total_size += dsize 287 | n = line["name"] + "/" 288 | print(f"{n:<25s}\t{dsize:>9d}\tDIR") 289 | continue 290 | 291 | total_size += line["size"] 292 | print(f"{line['name']:<25s}\t{line['size']:>9d}") 293 | 294 | print(f"Total: {total_size}") 295 | -------------------------------------------------------------------------------- /flipperzero_protobuf/flipperCmd/__init__.py: -------------------------------------------------------------------------------- 1 | """FlipperCMD Class Init""" 2 | 3 | from ..cli_helpers import * 4 | from .flipperCmd import FlipperCMD, cmdException 5 | 6 | __all__ = ["FlipperCMD", "cmdException"] 7 | 8 | __pdoc__ = { 9 | "flipperzero_protobuf_compiled": False, 10 | # 'flipperzero_protobuf.flipperCmd': False 11 | } 12 | -------------------------------------------------------------------------------- /flipperzero_protobuf/flipperCmd/__main__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """command-line interface package start hook""" 3 | 4 | from .flipperzero_cmd import main 5 | 6 | if __name__ == "__main__": 7 | main() 8 | -------------------------------------------------------------------------------- /flipperzero_protobuf/flipperCmd/cmd_complete.py: -------------------------------------------------------------------------------- 1 | """ 2 | command Completion callback class for FlipperCMD interface. 3 | """ 4 | 5 | import readline 6 | import sys 7 | from os import environ 8 | 9 | __all__ = ["Cmd_Complete"] 10 | 11 | 12 | class Cmd_Complete: # Custom completer 13 | """Command Completion callback class for FlipperCMD interface.""" 14 | 15 | def __init__(self, **kwargs): 16 | self.volcab = kwargs.get("volcab", []) 17 | self.volcab.sort() 18 | 19 | # self.cmd_comp_key = [] 20 | self.cmd_comp_cache = {} 21 | self.prompt = ">" 22 | 23 | def setup(self, volcab=None): 24 | """Command Completion setup callback.""" 25 | 26 | if volcab: 27 | self.volcab = sorted(volcab) 28 | 29 | self.prompt = "Flipper>>" 30 | readline.parse_and_bind("tab: complete") 31 | readline.set_completer(self.cmd_complete) 32 | 33 | completer_delims = readline.get_completer_delims() 34 | completer_delims = completer_delims.replace("-", "") 35 | readline.set_completer_delims(completer_delims) 36 | 37 | # readline.set_completion_display_matches_hook(self.display_matches) 38 | 39 | def cmd_complete(self, text, state) -> list: 40 | """Command Completion callback hook.""" 41 | 42 | # print(f"Call '{text}' {state}") 43 | buf = readline.get_line_buffer() 44 | # print(f"buf= >{buf}<") 45 | # print() 46 | # ct = readline.get_completion_type() 47 | # print(f"ct={ct}\n\n") 48 | 49 | # if buf.endswith('?'): 50 | # print help syntax 51 | 52 | # only do completion for first word 53 | if buf and buf[-1] == " " and buf.strip().upper() in self.volcab: 54 | return [None] 55 | 56 | text = text.upper() 57 | if text in self.cmd_comp_cache: 58 | # print(f"Cache {text} {state}", self.cmd_comp_cache[text]) 59 | return self.cmd_comp_cache[text][state] 60 | 61 | results = [x for x in self.volcab if x.startswith(text)] + [None] 62 | self.cmd_comp_cache[text] = results 63 | return results[state] 64 | 65 | # https://stackoverflow.com/questions/20625642/autocomplete-with-readline-in-python3 66 | 67 | def display_matches(self, _substitution, matches, _longest_match_length): 68 | """Command Completion display callback hook.""" 69 | # line_buffer = readline.get_line_buffer() 70 | columns = environ.get("COLUMNS", 80) 71 | 72 | tpl = "{:<" + str(int(_longest_match_length * 1.2)) + "}" 73 | 74 | # print(f"substitution={substitution}") 75 | # print(f"matches={matches}") 76 | # print(f"_longest_match_length={_longest_match_length}") 77 | # print(f"tpl={tpl}") 78 | 79 | buffer = "" 80 | for match in matches: 81 | match = tpl.format(match) 82 | if len(buffer + match) > columns: 83 | print(buffer) 84 | buffer = "" 85 | buffer += match 86 | 87 | print(self.prompt.rstrip(), readline.get_line_buffer(), sep="", end="") 88 | 89 | sys.stdout.flush() 90 | -------------------------------------------------------------------------------- /flipperzero_protobuf/flipperCmd/flipperCmd.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """ command function and methods for command line app. 4 | 5 | FlipperCMD 6 | 7 | """ 8 | # Written By: Peter Shipley github.com/evilpete 9 | # 10 | # From python flipperzero_protobuf pkg 11 | 12 | # pylint: disable=line-too-long, no-member, unused-import, unused-argument 13 | # pylint bug reports "Deprecated module 'string' (deprecated-module)" 14 | import os 15 | import readline 16 | import string # pylint: disable=deprecated-module 17 | import sys 18 | import time 19 | import zipfile 20 | 21 | from ..cli_helpers import ( 22 | calc_file_md5, 23 | calc_n_print_du, 24 | dict2datetime, 25 | flipper_tree_walk, 26 | print_screen, 27 | ) 28 | 29 | # from google.protobuf.json_format import MessageToDict 30 | # from ..flipper_base import FlipperProtoException # cmdException # FlipperProtoBase 31 | # from .flipper_storage import FlipperProtoStorage 32 | from ..flipper_proto import FlipperProto 33 | 34 | # import pprint 35 | 36 | 37 | _DEBUG = 0 38 | 39 | __all__ = ["FlipperCMD", "cmdException"] 40 | 41 | # 42 | # the docstring for class methods should be in the form of 43 | # description on first line then syntax on the seconds line o 44 | # 45 | # """display disk usage statistic 46 | # du 47 | # """ 48 | # 49 | # 50 | 51 | VALID_STR = string.ascii_letters + string.digits + "$%-_@~`!." 52 | 53 | 54 | # valid_set = set(valid_str) 55 | class cmdException(Exception): 56 | """ 57 | Flipper Command Exception 58 | """ 59 | 60 | def __init__(self, msg): 61 | Exception.__init__(self, msg) 62 | 63 | 64 | class FlipperCMD: 65 | # ppylint: disable=too-many-instance-attributes, too-many-public-methods 66 | """ 67 | command call function 68 | 69 | 70 | Raises 71 | ---------- 72 | cmdException 73 | """ 74 | 75 | class QuitException(Exception): 76 | """ 77 | Exception to exit App 78 | """ 79 | 80 | def __init__(self, msg): 81 | Exception.__init__(self, msg) 82 | 83 | def __init__(self, proto=None, **kwargs): 84 | self.debug = kwargs.get("debug", _DEBUG) 85 | 86 | if proto is None: 87 | serial_port = kwargs.get("serial_port", None) 88 | self.flip = FlipperProto(serial_port=serial_port, debug=self.debug) 89 | 90 | self._cmd_table = {} 91 | self._gen_cmd_table() 92 | 93 | self.rdir = "/ext" 94 | self.prevError = "OK" 95 | self._local_time = time.localtime() 96 | self.color_ls = False 97 | 98 | self.valid_set = set(VALID_STR) 99 | 100 | self.verbose = kwargs.get("verbose", 0) 101 | 102 | self.excludes = [ 103 | ".thumbs", 104 | ".AppleDouble", 105 | ".RECYCLER", 106 | ".Spotlight-V100", 107 | "__pycache__", 108 | ] 109 | 110 | def _gen_cmd_table(self): 111 | """_gen_cmd_table doc""" 112 | 113 | # has to be in method to reference itself 114 | self.cmd_set = { 115 | ("LS", "LIST"): self.do_list, 116 | ("RM", "DEL", "DELETE"): self.do_del, 117 | ("MV", "RENAME"): self.do_rename, 118 | ("DU", "DISK-USAGE"): self._do_disk_usage, 119 | ("MD", "MKDIR"): self.do_mkdir, 120 | ("MD5SUM", "MD5"): self.do_md5sum, 121 | ("CAT",): self.do_cat_file, 122 | ("GET", "GETFILE"): self.do_get_file, 123 | ("GETT",): self.new_get_file, 124 | ("PUTT",): self.new_put_file, 125 | ("GET-TREE", "GETTREE"): self.do_get_tree, 126 | ("PUT", "PUTFILE"): self.do_put_file, 127 | ("PUT-TREE", "PUTTREE"): self.do_put_tree, 128 | ("TIMESTAMP",): self.do_timestamp, 129 | ("STAT",): self.do_stat, 130 | ("SET",): self._set_opt, 131 | ("TIME",): self.do_time, 132 | ("DF", "INFO"): self.do_info, 133 | ("DEV-INFO",): self.do_devinfo, 134 | ("GET-PROP", "GETPROP"): self.do_get_property, 135 | ("GET-ERROR", "GETERR"): self.do_get_error, 136 | ("LCD", "LCHDIR"): self._do_chdir, 137 | ("LPWD",): self._do_print_cwd, 138 | ("PRINT-SCREEN",): self.do_print_screen, 139 | ("CD", "CHDIR", "RCD"): self._set_rdir, 140 | # ("PWD",):, 141 | ("HISTORY", "HIST"): self._print_cmd_hist, # Do we need this? 142 | ("STOP-SESSION",): self.do_stop_session, 143 | ("START-SESSION",): self.do_start_session, 144 | ("SEND", "SEND-COMMAND"): self._do_send_cmd, 145 | ("DESKTOP_IS_LOCKED",): self.do_desktop_is_locked, 146 | ("DESKTOP_UNLOCK",): self.do_desktop_unlock, 147 | ("REBOOT",): self.do_reboot, 148 | ("RUN-APP", "RUN"): self.do_run_app, 149 | ("XCHG", "DATA-XCHANGE"): self.do_data_exchange, 150 | ("QUIT", "EXIT"): self.do_quit, 151 | ("ZIP",): self._do_zip, 152 | ("UNTAR",): self.do_untar, 153 | ("HELP", "?"): self.print_cmd_help, 154 | } 155 | 156 | for k, v in self.cmd_set.items(): 157 | # print(f"len {type(k)} k{len(k)} {k}") 158 | for c in k: 159 | self._cmd_table[c] = v 160 | 161 | def get_cmd_keys(self) -> list: 162 | """returns list of commands""" 163 | return self._cmd_table.keys() 164 | 165 | def run_comm(self, argv) -> None: 166 | """run command line""" 167 | cmd = argv.pop(0).upper() 168 | 169 | if cmd == "SLEEP": 170 | sleep_time = 5 171 | if argv: 172 | sleep_time = int(argv[0]) 173 | if self.verbose: 174 | print(f"{cmd} {sleep_time}") 175 | time.sleep(sleep_time) 176 | return 177 | 178 | if cmd in ["ECHO", "PRINT"]: 179 | print(" ".join(argv)) 180 | return 181 | 182 | if cmd in self._cmd_table: 183 | # handle help here insteqd of everywhere else 184 | if argv and argv[0] == "?": 185 | print("==") 186 | print(self._get_cmd_syntax(cmd)) 187 | return 188 | 189 | self._cmd_table[cmd](cmd, argv) 190 | else: 191 | print("Unknown command : ", cmd) # str(" ").join(argv) 192 | 193 | def do_quit(self, cmd, argv): # pylint: disable=unused-argument 194 | """Exit Program 195 | QUIT 196 | """ 197 | raise self.QuitException("Quit interactive mode") 198 | 199 | def _do_print_cwd(self, cmd, argv): # pylint: disable=unused-argument 200 | """print local working directory 201 | LPWD 202 | """ 203 | print(os.getcwd()) 204 | 205 | def do_cmd_help(self, cmd, argv): # pylint: disable=unused-argument 206 | """print command list""" 207 | self.print_cmd_help(cmd, argv) 208 | 209 | # prints first line of __doc__ string 210 | def print_cmd_help(self, cmd, argv): # pylint: disable=unused-argument 211 | """print command list""" 212 | 213 | if argv and argv[0].upper() in self._cmd_table: 214 | hcmd = argv[0].upper() 215 | if self._cmd_table[hcmd].__doc__: 216 | hlist = self._cmd_table[hcmd].__doc__.split("\n") 217 | print(hlist[0]) 218 | print("\t", hlist[1].strip()) 219 | return 220 | 221 | for k, v in sorted(self.cmd_set.items()): 222 | if v.__doc__: 223 | print(f" {' '.join(k):<20s}:", v.__doc__.split("\n")[0].strip()) 224 | 225 | def _get_cmd_syntax(self, cmdname): 226 | if cmdname in self._cmd_table and self._cmd_table[cmdname].__doc__: 227 | ret = self._cmd_table[cmdname].__doc__.split("\n")[1].rstrip() 228 | else: 229 | ret = cmdname 230 | 231 | return ret 232 | 233 | # Do we need this ?? 234 | def _print_cmd_hist(self, cmd, argv): 235 | """Print command history 236 | history [count] 237 | """ 238 | 239 | show_count = 20 240 | 241 | if argv: 242 | if argv[0].upper() in ["?", "HELP"]: 243 | raise cmdException( 244 | f"Syntax:\n\t{cmd} [count]\n" "\tprint command history" 245 | ) 246 | 247 | if argv[0].lstrip("-").isdigit(): 248 | show_count = int(argv[0].lstrip("-")) 249 | 250 | start_at = readline.get_current_history_length() - show_count 251 | # print(f"{start_at} = {readline.get_current_history_length()} - {show_count}") 252 | start_at = max(start_at, 0) 253 | 254 | # print(f"show_count={show_count} start_at={start_at}") 255 | # for hist in self.cmdHistory[start_at:]: 256 | # print(f" {hist}") 257 | 258 | for i in range(start_at, readline.get_current_history_length()): 259 | print(str(readline.get_history_item(i + 1))) 260 | 261 | def do_devinfo(self, cmd, argv): # pylint: disable=unused-argument 262 | """print device info 263 | Dev-Info 264 | """ 265 | if argv and argv[0] in self.flip.device_info: 266 | print(f"{argv[0]:<25s} = {self.flip.device_info[argv[0]]}") 267 | return 268 | 269 | for k, v in sorted(self.flip.device_info.items()): 270 | print(f"{k:<25s} = {v}") 271 | 272 | def do_get_property(self, cmd, argv): 273 | """print property by key 274 | GET-PROP 275 | """ 276 | if len(argv) != 1: 277 | raise cmdException(f"Syntax:\n\t{cmd} \n" "\tprint property by key") 278 | 279 | props = self.flip.rpc_property_get(argv[0]) 280 | for k, v in props: 281 | print(f"{k:<28s} = {v}") 282 | 283 | def do_get_error(self, cmd, argv): 284 | """print extended error info (code and text description) 285 | GET-ERROR 286 | """ 287 | if len(argv): 288 | raise cmdException(f"Syntax:\n\t{cmd}\n" "\tprint extended error info") 289 | err = self.flip.rpc_app_get_error() 290 | print(f'Code: {err[0]}, text: "{err[1]}"') 291 | 292 | def _interpret_val(self, opt): 293 | opt = opt.upper() 294 | 295 | if opt in ["ON", "TRUE", "T", "YES", "Y", "1"]: 296 | return 1 297 | 298 | if opt in ["OFF", "FALSE", "F", "NO", "N", "0"]: 299 | return 0 300 | 301 | if opt.isdigit(): 302 | return int(opt) 303 | 304 | return None 305 | 306 | def _set_opt(self, cmd, argv): # pylint: disable=unused-argument 307 | """set or print current option value 308 | SET [Key Value] 309 | """ 310 | if len(argv) < 2: 311 | excl = " ".join(self.excludes) 312 | print( 313 | f"\tverbose:\t{self.verbose}\n" 314 | f"\tdebug: \t{self.debug}\n" 315 | f"\tColor: \t{self.color_ls}\n" 316 | f"\tremote-dir:\t{self.rdir}\n" 317 | f"\tPort: \t{self.flip.port()}\n" 318 | f"\texcludes: \t{excl}\n" 319 | f"\tCWD: \t{os.getcwd()}\n" 320 | ) 321 | return 322 | 323 | # print(f"set_opt {argv[0].upper()}") 324 | if argv[0].upper() == "COLOR": 325 | val = self._interpret_val(argv[1]) 326 | if val is not None: 327 | self.color_ls = val 328 | return 329 | 330 | if argv[0].upper() == "DEBUG": 331 | val = self._interpret_val(argv[1]) 332 | if val is not None: 333 | self.debug = val 334 | return 335 | 336 | if argv[0].upper() == "EXCLUDES": 337 | self.excludes = argv[1:] 338 | return 339 | 340 | if argv[0].upper() == "REMOTE-DIR": 341 | self._set_rdir(argv[0], argv[1:]) 342 | return 343 | 344 | if argv[0].upper() == "VERBOSE": 345 | val = self._interpret_val(argv[1]) 346 | if val is not None: 347 | self.verbose = val 348 | return 349 | 350 | raise cmdException(f"{cmd}: {argv[0]}: value not recognised") 351 | 352 | def do_time(self, cmd, argv): # pylint: disable=unused-argument 353 | """Set or Get Current time from Flipper 354 | time [SET] 355 | """ 356 | set_time = False 357 | if argv: 358 | if argv[0].upper() in ["SET", "SET-TIME"]: 359 | set_time = True 360 | else: 361 | print(f"Unknown Arg/Option: {argv[0]}") 362 | # raise cmdException(f"Syntax:\n\t{cmd} [SET]\n" 363 | # "\tdisplay time") 364 | 365 | if set_time: 366 | # system time used it called without arg 367 | self.flip.rpc_set_datetime() 368 | 369 | dtime_resp = self.flip.rpc_get_datetime() 370 | 371 | dt = dict2datetime(dtime_resp) 372 | print(dt.ctime()) 373 | 374 | def _remote_path(self, path) -> str: 375 | if path.startswith("/"): 376 | return path 377 | 378 | return os.path.normpath(self.rdir + "/" + path) 379 | 380 | def _do_disk_usage(self, cmd, argv): 381 | """display disk usage statistic 382 | DU 383 | """ 384 | if not argv or argv[0] == "?": 385 | raise cmdException(f"Syntax:\n\t{cmd} \n" "\tdisplay disk usage") 386 | 387 | targ = self._remote_path(argv.pop(0)) 388 | 389 | calc_n_print_du(self.flip, targ) 390 | 391 | def _do_zip(self, cmd, argv): 392 | """Generate Zip Archive 393 | ZIP 394 | """ 395 | if not argv or argv[0] == "?" or len(argv) < 2: 396 | raise cmdException( 397 | f"Syntax:\n\t{cmd} \n" "\tGenerate Zip Archive" 398 | ) 399 | 400 | zipfilename = argv.pop(0) 401 | if not zipfilename.endswith((".zip", ".ZIP")): 402 | zipfilename = zipfilename + ".zip" 403 | 404 | self._local_time = time.localtime() 405 | with zipfile.ZipFile(zipfilename, "w", zipfile.ZIP_DEFLATED) as zf: 406 | for targ in argv: 407 | targ = self._remote_path(targ) 408 | 409 | # pylint: disable=protected-access 410 | targ_stat = self.flip._rpc_stat(targ) 411 | 412 | if targ_stat is None: 413 | print(f"{targ}: Not found") 414 | continue 415 | 416 | if targ_stat["type"] != "DIR": 417 | self._zip_add_file(zf, targ) 418 | continue 419 | 420 | for ROOT, DIRS, FILES in flipper_tree_walk(targ, self.flip): 421 | for d in DIRS: 422 | fpath = f"{ROOT}/{d}/" 423 | print(f"zip: adding {fpath}") 424 | zfi = zipfile.ZipInfo(fpath) 425 | zfi.compress_type = zipfile.ZIP_STORED 426 | zfi.external_attr = (0o040755 << 16) | 0x10 427 | zf.writestr(zfi, "") 428 | 429 | for f in FILES: 430 | fpath = f"{ROOT}/{f}" 431 | self._zip_add_file(zf, fpath) 432 | 433 | def _zip_add_file(self, zf, fpath): 434 | print(f"zip: adding {fpath}") 435 | file_data = self.flip.rpc_read(fpath) 436 | zfi = zipfile.ZipInfo(fpath) 437 | zfi.date_time = self._local_time[:6] 438 | zfi.compress_type = zipfile.ZIP_STORED 439 | zfi.external_attr = 0o0644 << 16 440 | zf.writestr(zfi, file_data) 441 | 442 | def _do_send_cmd(self, cmd, argv): # pylint: disable=unused-argument 443 | """Semd non rpc command to flipper 444 | SEND 445 | """ 446 | cmd_str = " ".join(argv) 447 | self.flip.send_cmd(cmd_str) 448 | 449 | # pylint: disable=protected-access 450 | def _set_rdir(self, cmd, argv) -> None: 451 | """change current directory on flipper 452 | cd \n 453 | """ 454 | if len(argv) == 0 or argv[0] == "?" or len(argv) > 1: 455 | raise cmdException(f"Syntax:\n\t{cmd} \n" "\tset remote directory") 456 | 457 | remdir = argv.pop(0) 458 | 459 | if remdir.startswith("/"): 460 | newdir = remdir 461 | else: 462 | newdir = os.path.normpath(self.rdir + "/" + remdir) 463 | 464 | if newdir in ["/", "/ext", "/int"]: 465 | self.rdir = newdir 466 | print(f"Remote directory: {newdir}") 467 | return 468 | 469 | stat_resp = self.flip._rpc_stat(newdir) 470 | 471 | if stat_resp is None: 472 | print(f"{newdir}: Not found") 473 | elif stat_resp["type"] == "DIR": 474 | self.rdir = newdir 475 | if self.verbose: 476 | print(f"Remote directory: {newdir}") 477 | else: 478 | print("{newdir}: not a directory") 479 | 480 | def do_print_screen(self, cmd, argv): 481 | """Take screendump in ascii or PBM format 482 | PRINT-SCREEN [filename.pbm] 483 | """ 484 | outf = None 485 | if len(argv) == 0 or argv[0] == "?" or len(argv) > 1: 486 | raise cmdException( 487 | f"Syntax:\n\t{cmd} [filename.pbm]\n" 488 | "\tfile has to end in .pbm\n" 489 | "\tif no file is given image is printed to stdout" 490 | ) 491 | 492 | if argv: 493 | outf = argv.pop(0) 494 | print_screen(self.flip.rpc_gui_snapshot_screen(), outf) 495 | 496 | def _show_flist(self, filelist): 497 | """format list into columns and print""" 498 | 499 | max_len = max(map(len, filelist)) 500 | 501 | max_len = max(max_len, 8) 502 | 503 | # columns = environ.get("COLUMNS", 80) 504 | columns = os.get_terminal_size()[0] - 2 505 | # print(f"columns={columns}") 506 | 507 | tpl = "{:<" + str(int(max_len * 1.2)) + "}" 508 | # print(f"tpl={tpl}") 509 | 510 | buffer = " " 511 | for f in filelist: 512 | if self.color_ls and f[-1] == "/": 513 | f = self._blue(f) 514 | f = tpl.format(f) 515 | if len(buffer + f) > columns: 516 | print(buffer) 517 | buffer = " " 518 | buffer += f 519 | 520 | print(buffer) 521 | 522 | # storage_pb2.File.DIR == 1 523 | # storage_pb2.File.FILE == 0 524 | # Colors 525 | @staticmethod 526 | def _pink(t): 527 | """display pink text""" 528 | return f"\033[95m{t}\033[0m" 529 | 530 | @staticmethod 531 | def _blue(t): 532 | """display blue text""" 533 | return f"\033[94m{t}\033[0m" 534 | 535 | # def magenta(t): return f'\033[93m{t}\033[0m' 536 | # def yellow(t): return f'\033[93m{t}\033[0m' 537 | # def green(t): return f'\033[92m{t}\033[0m' 538 | # def red(t): return f'\033[91m{t}\033[0m' 539 | 540 | # 541 | # rpc call storage_list_request only takes folders as 542 | # a valid arg. 543 | # trailing '/' are mot allowed 544 | # 545 | # rpc call storage_stat_request returns None for 546 | # '/', '/ext', '/any', '/int' 547 | # trailing '/' are mot allowed 548 | # 549 | # thus we have to special case the f*ck out of the list command 550 | # 551 | def do_list(self, cmd, argv): 552 | """list files and dirs on Flipper device 553 | ls [-l] [-m] 554 | """ 555 | # pylint: disable=protected-access 556 | 557 | long_format = False 558 | md5_format = False 559 | 560 | while argv: 561 | if argv[0] in ["-help", "?"]: 562 | raise cmdException(f"Syntax :\n\t{cmd} [-l] [-m] ") 563 | 564 | if argv[0][0] != "-": 565 | break 566 | 567 | arg = argv.pop(0) 568 | for a in arg.lstrip("-"): 569 | if a == "m": 570 | md5_format = True 571 | long_format = True 572 | continue 573 | if a == "l": 574 | long_format = True 575 | 576 | if argv: 577 | targ = argv.pop(0) 578 | else: 579 | targ = self.rdir # current DIR 580 | 581 | if not targ.startswith("/"): # check for full path 582 | # targ = '/ext/' + targ 583 | targ = os.path.normpath(self.rdir + "/" + targ) 584 | 585 | # strip trailing slash 586 | if len(targ) > 1: 587 | targ = targ.rstrip("/") 588 | 589 | # check if targ is a file 590 | if targ not in ["/", "/any", "/ext", "/int"]: # special Cases 591 | stat_resp = self.flip._rpc_stat(targ) 592 | md5val = "" 593 | if stat_resp is not None and stat_resp.get("type", "") == "FILE": 594 | if md5_format: 595 | md5val = self.flip.rpc_md5sum(targ) 596 | print(f"{targ:<25s}\t{stat_resp['size']:>6d}", md5val) 597 | return 598 | 599 | flist = self.flip.rpc_storage_list(targ) 600 | 601 | if flist: 602 | flist.sort(key=lambda x: (x["type"], x["name"].lower())) 603 | 604 | # should split off file list printing into sub func 605 | if not long_format: 606 | fl = [ 607 | line["name"] if line["type"] != "DIR" else line["name"] + "/" 608 | for line in flist 609 | ] 610 | self._show_flist(fl) 611 | else: 612 | md5val = "" 613 | sizetotal = 0 614 | 615 | # print(f"{targ}:") 616 | for line in flist: 617 | if line["type"] == "DIR": 618 | # print(dir_fmt.format(line['name']) 619 | print(f"{line['name']:<25s}\t DIR") 620 | else: 621 | sizetotal += line["size"] 622 | if md5_format: 623 | md5val = self.flip.rpc_md5sum(targ + "/" + line["name"]) 624 | 625 | print(f"{line['name']:<25s}\t{line['size']:>6d}", md5val) 626 | 627 | print(f"Total Bytes: {sizetotal}") 628 | 629 | # add blank line 630 | print() 631 | 632 | def do_del(self, cmd, argv): 633 | """delete file of directory on flipper device 634 | DEL [-r] [file] file] 635 | """ 636 | error_str = f"Syntax :\n\t{cmd} [-r] file" 637 | # if not argv or argv[0] == '?': 638 | # raise cmdException(error_str) 639 | 640 | recursive = False 641 | if argv and argv[0] in ["-r", "-R"]: 642 | argv.pop(0) 643 | recursive = True 644 | 645 | if not argv: 646 | raise cmdException(error_str) 647 | 648 | for targ in argv: 649 | targ = targ.rstrip("/") 650 | 651 | if not targ.startswith("/"): 652 | targ = os.path.normpath(self.rdir + "/" + targ) 653 | 654 | if self.verbose: 655 | print(f"{cmd} {'-r' if recursive else ''} {targ}") 656 | 657 | self.flip.rpc_delete(targ, recursive=recursive) 658 | 659 | def do_rename(self, cmd, argv): 660 | """rename file or dir 661 | RENAME 662 | renames or move file on flipper device 663 | """ 664 | if len(argv) < 2 or (argv and argv[0] == "?"): 665 | raise cmdException(f"Syntax :\n\t{cmd} ") 666 | 667 | argv_len = len(argv) 668 | 669 | if argv_len == 2: 670 | old_fn = argv.pop(0).rstrip("/") 671 | new_fn = argv.pop(0).rstrip("/") 672 | 673 | if not old_fn.startswith("/"): 674 | old_fn = os.path.normpath(self.rdir + "/" + old_fn) 675 | 676 | if not new_fn.startswith("/"): 677 | new_fn = os.path.normpath(self.rdir + "/" + new_fn) 678 | 679 | # is destination a directory? 680 | stat_resp = self.flip._rpc_stat(new_fn) 681 | if stat_resp is not None and stat_resp.get("type", "") == "DIR": 682 | bname = os.path.basename(old_fn) 683 | new_fn = f"{new_fn}/{bname}" 684 | 685 | if self.debug: 686 | print(cmd, old_fn, new_fn) 687 | 688 | self.flip.rpc_rename_file(old_fn, new_fn) 689 | return 690 | 691 | # argv_len > 2 692 | dest_dir = argv.pop(-1).rstrip("/") 693 | flist = argv 694 | 695 | if not dest_dir.startswith("/"): 696 | dest_dir = os.path.normpath(self.rdir + "/" + dest_dir) 697 | 698 | # is destination a directory? 699 | stat_resp = self.flip._rpc_stat(dest_dir) 700 | if stat_resp is not None and stat_resp.get("type", "") != "DIR": 701 | print(f"{dest_dir}: Not a directory") 702 | return 703 | 704 | for f in flist: 705 | dist_file = f"{dest_dir}/{os.path.basename(f)}" 706 | if not f.startswith("/"): 707 | f = os.path.normpath(self.rdir + "/" + f) 708 | 709 | if self.verbose: 710 | print(f"get {f} -> {dist_file}\n") 711 | 712 | self.flip.rpc_rename_file(f, dist_file) 713 | 714 | def _mkdir_path(self, targ): 715 | """Simplified mkdir for internal use""" 716 | 717 | subpath = "" 718 | for p in targ.split("/"): 719 | if not p: 720 | continue 721 | 722 | subpath = subpath + "/" + p 723 | self.flip._mkdir_path(subpath) 724 | 725 | def do_mkdir(self, cmd, argv): 726 | """Create a new directory 727 | MKDIR 728 | Make directories on flipper device 729 | """ 730 | if len(argv) == 0 or argv[0] == "?" or len(argv) > 1: 731 | raise cmdException(f"Syntax :\n\t{cmd} ") 732 | 733 | targ = argv.pop(0) 734 | if not targ.startswith("/"): 735 | targ = os.path.normpath(self.rdir + "/" + targ) 736 | 737 | if self.debug or self.verbose: 738 | print(cmd, targ) 739 | 740 | self.flip.rpc_mkdir(targ) 741 | 742 | def _do_chdir(self, cmd, argv): 743 | """Change local current directory 744 | LCD 745 | """ 746 | # pylint: disable=broad-except, unused-argument 747 | if len(argv) == 0 or argv[0] == "?" or len(argv) > 1: 748 | raise cmdException(f"Syntax :\n\t{cmd} ") 749 | 750 | targ = argv.pop(0) 751 | 752 | try: 753 | os.chdir(targ) 754 | except FileNotFoundError as _e: 755 | print(f"No such file or directory: {targ}") 756 | except NotADirectoryError as _e: 757 | print(f"Not a directory: {targ}") 758 | except Exception as _e: 759 | print(f"CHDIR: {_e}") 760 | 761 | def do_md5sum(self, cmd, argv): 762 | """md5 hash of the file 763 | MD5 764 | """ 765 | if len(argv) == 0 or argv[0] == "?" or len(argv) > 1: 766 | raise cmdException(f"Syntax :\n\t{cmd} file") 767 | 768 | targ = argv.pop(0) 769 | 770 | if not targ.startswith("/"): 771 | targ = os.path.normpath(self.rdir + "/" + targ) 772 | 773 | if self.debug: 774 | print(cmd, targ) 775 | 776 | md5sum_resp = self.flip.rpc_md5sum(targ) 777 | print(f"md5sum_resp={md5sum_resp}") 778 | 779 | def do_cat_file(self, cmd, argv): 780 | """read flipper file to screen 781 | cat 782 | """ 783 | if len(argv) == 0 or argv[0] == "?" or len(argv) > 1: 784 | raise cmdException(f"Syntax :\n\t{cmd} file") 785 | 786 | remote_filen = argv.pop(0) 787 | 788 | if not remote_filen.startswith("/"): 789 | remote_filen = os.path.normpath(self.rdir + "/" + remote_filen) 790 | 791 | if self.debug: 792 | print(cmd, remote_filen) 793 | 794 | read_resp = self.flip.rpc_read(remote_filen) 795 | # print("cmd_read {len(read_resp)}") 796 | print(read_resp.decode("utf-8")) 797 | 798 | def _get_file(self, remote_filen, local_filen): 799 | """Simplified get file for internal use""" 800 | 801 | if self.verbose: 802 | print(f"get {remote_filen} -> {local_filen}\n") 803 | 804 | file_data = self.flip.rpc_read(remote_filen) 805 | with open(local_filen, "wb") as fd: 806 | fd.write(file_data) 807 | 808 | def _valid_filename(self, fname): 809 | # print(f"_valid_filename: {fname}") 810 | # print(set(fname) - self.valid_set) 811 | # return set(fname) - self.valid_set 812 | return set(fname) <= self.valid_set 813 | 814 | def new_get_file(self, cmd, argv): 815 | """copy file from flipper 816 | GET 817 | """ 818 | 819 | print("new_get_file:", cmd, argv) 820 | 821 | arg_len = len(argv) 822 | 823 | if arg_len == 1: 824 | print("arg_len 1") 825 | 826 | remote_filen = argv.pop(0) 827 | local_filen = os.path.basename(remote_filen) 828 | 829 | if not remote_filen.startswith("/"): 830 | remote_filen = os.path.normpath(self.rdir + "/" + remote_filen) 831 | 832 | self._get_file(remote_filen, local_filen) 833 | return 834 | 835 | if arg_len == 2: 836 | print("arg_len 2") 837 | 838 | remote_filen = argv.pop(0) 839 | local_filen = argv.pop(0) 840 | 841 | if os.path.isdir(local_filen): 842 | local_name = local_filen + "/" + os.path.basename(remote_filen) 843 | 844 | if not remote_filen.startswith("/"): 845 | remote_name = os.path.normpath(self.rdir + "/" + remote_filen) 846 | 847 | self._get_file(remote_name, local_name) 848 | return 849 | 850 | # arg_len > 2 851 | print("arg_len 3") 852 | local_filen = argv.pop(-1) 853 | remote_list = argv 854 | 855 | # print("remote_list:", remote_list) 856 | # print("local_filen:", local_filen) 857 | if not os.path.isdir(local_filen): 858 | print(f"{local_filen}: Not a directory") 859 | return 860 | 861 | for f in remote_list: 862 | local_name = local_filen + "/" + os.path.basename(f) 863 | 864 | if not f.startswith("/"): 865 | remote_name = os.path.normpath(self.rdir + "/" + f) 866 | 867 | self._get_file(remote_name, local_name) 868 | 869 | def do_get_file(self, cmd, argv): 870 | """copy file from flipper 871 | GET [local_filename] 872 | """ 873 | 874 | if len(argv) < 1 or argv[0] == "?": 875 | raise cmdException(f"Syntax :\n\t{cmd} ") 876 | 877 | remote_filen = argv.pop(0) 878 | if argv: 879 | local_filen = argv.pop(0) 880 | else: 881 | local_filen = "." 882 | 883 | if os.path.isdir(local_filen): 884 | local_filen = local_filen + "/" + os.path.basename(remote_filen) 885 | 886 | if not remote_filen.startswith("/"): 887 | remote_filen = os.path.normpath(self.rdir + "/" + remote_filen) 888 | 889 | # is destination a directory? 890 | # if os.path.isdir(local_filen): 891 | # bname = os.path.basename(remote_filen) 892 | # local_filen = local_filen + '/' + bname 893 | 894 | if self.debug: 895 | print(cmd, remote_filen, local_filen) 896 | 897 | if self.verbose: 898 | print(f"get {remote_filen} -> {local_filen}\n") 899 | 900 | # self._get_file(remote_filen, local_filen) 901 | 902 | # read data from flipper 903 | file_data = self.flip.rpc_read(remote_filen) 904 | 905 | # print(f"getting {len(file_data)} bytes") 906 | with open(local_filen, "wb") as fd: 907 | fd.write(file_data) 908 | 909 | def _put_file(self, local_filen, remote_filen): 910 | """Simplified put file""" 911 | if self.verbose: 912 | print(f"copy {local_filen} -> {remote_filen}") 913 | 914 | with open(local_filen, "rb") as fd: 915 | file_data = fd.read() 916 | 917 | self.flip.rpc_write(remote_filen, file_data) 918 | 919 | def new_put_file(self, cmd, argv): 920 | """copy file to flipper 921 | PUT 922 | """ 923 | if len(argv) < 1 or argv[0] == "?": 924 | raise cmdException(f"Syntax :\n\t{cmd} ") 925 | 926 | arg_len = len(argv) 927 | 928 | if arg_len == 1: 929 | local_filen = argv.pop(0) 930 | remote_filen = os.path.normpath( 931 | self.rdir + "/" + os.path.basename(local_filen) 932 | ) 933 | 934 | self._put_file(local_filen, remote_filen) 935 | return 936 | 937 | if arg_len == 2: 938 | local_filen = argv.pop(0) 939 | remote_filen = argv.pop(0) 940 | 941 | if not remote_filen.startswith("/"): 942 | remote_filen = os.path.normpath(self.rdir + "/" + remote_filen) 943 | 944 | # is destination a directory? 945 | stat_resp = self.flip._rpc_stat(remote_filen) 946 | if stat_resp is not None and stat_resp.get("type", "") == "DIR": 947 | bname = os.path.basename(local_filen) 948 | remote_filen = remote_filen + "/" + bname 949 | 950 | self._put_file(local_filen, remote_filen) 951 | return 952 | 953 | # arg_len > 2 954 | remote_filen = argv.pop(-1) 955 | local_list = argv 956 | 957 | if not remote_filen.startswith("/"): 958 | remote_filen = os.path.normpath(self.rdir + "/" + remote_filen) 959 | 960 | # is destination a directory? 961 | stat_resp = self.flip._rpc_stat(remote_filen) 962 | if stat_resp is not None and stat_resp.get("type", "") != "DIR": 963 | print(f"{remote_filen}: Not a directory") 964 | return 965 | 966 | for f in local_list: 967 | remote_name = remote_filen + "/" + os.path.basename(f) 968 | self._put_file(f, remote_name) 969 | 970 | def do_put_file(self, cmd, argv): 971 | """copy file to flipper 972 | PUT 973 | """ 974 | if len(argv) < 1 or argv[0] == "?": 975 | raise cmdException(f"Syntax :\n\t{cmd} ") 976 | 977 | local_filen = argv.pop(0) 978 | if argv: 979 | remote_filen = argv.pop(0) 980 | else: 981 | remote_filen = local_filen 982 | 983 | if self.debug: 984 | print(cmd, local_filen, remote_filen) 985 | 986 | if not os.path.exists(local_filen): 987 | print(f"can not open {local_filen}") 988 | elif os.path.isdir(local_filen): 989 | print(f"Is a directory {local_filen}") 990 | print("Use PUT-TREE command instead") 991 | return 992 | 993 | if not remote_filen.startswith("/"): 994 | remote_filen = os.path.normpath(self.rdir + "/" + remote_filen) 995 | 996 | # is destination a directory? 997 | stat_resp = self.flip._rpc_stat(remote_filen) 998 | if stat_resp is not None and stat_resp.get("type", "") == "DIR": 999 | bname = os.path.basename(local_filen) 1000 | remote_filen = remote_filen + "/" + bname 1001 | 1002 | # print(cmd, local_filen, remote_filen) 1003 | 1004 | if self.verbose: 1005 | print(f"copy {local_filen} -> {remote_filen}") 1006 | 1007 | # self._put_file(local_filen, remote_filen) 1008 | 1009 | with open(local_filen, "rb") as fd: 1010 | file_data = fd.read() 1011 | 1012 | # print(f"putting {len(file_data)} bytes") 1013 | self.flip.rpc_write(remote_filen, file_data) 1014 | 1015 | # this needs code to act like cp/rsync were if source_file ends in a /, 1016 | # the contents of the directory are copied rather than the directory itself 1017 | def do_put_tree(self, cmd, argv): 1018 | # pylint: disable=protected-access 1019 | """copy directory tree to flipper 1020 | PUT-TREE 1021 | If the source_file ends in a /, the contents of 1022 | the directory are copied rather than the directory itself. 1023 | """ 1024 | # excludes = [".thumbs", ".AppleDouble", ".RECYCLER", ".Spotlight-V100", '__pycache__'] 1025 | check_md5 = False 1026 | 1027 | verbose = self.debug or self.verbose 1028 | 1029 | syntax_str = f"Syntax :\n\t{cmd} [-md5] " 1030 | if not argv or argv[0] in ["?", "help"]: 1031 | raise cmdException(syntax_str) 1032 | 1033 | if argv and argv[0].upper() in ["-M", "-MD5"]: 1034 | argv.pop(0) 1035 | check_md5 = True 1036 | 1037 | if not argv: 1038 | raise cmdException(syntax_str) 1039 | 1040 | if argv: 1041 | local_dir = argv.pop(0) 1042 | 1043 | if argv: 1044 | remote_dir = argv.pop(0) 1045 | else: 1046 | remote_dir = self.rdir 1047 | 1048 | if not remote_dir.startswith("/"): 1049 | remote_dir = os.path.normpath(self.rdir + "/" + remote_dir) 1050 | 1051 | if not os.path.isdir(local_dir): 1052 | raise cmdException(f"{syntax_str}\n\t{local_dir}: not a directory") 1053 | 1054 | local_dir_full = os.path.abspath(local_dir) 1055 | local_dir_targ = os.path.split(local_dir_full)[1] 1056 | remote_dir_targ = os.path.split(remote_dir)[1] 1057 | 1058 | if local_dir.endswith("/") or local_dir_targ == remote_dir_targ: 1059 | self._mkdir_path(remote_dir) 1060 | else: 1061 | remote_dir = remote_dir + "/" + local_dir_targ 1062 | 1063 | stat_resp = self.flip._rpc_stat(remote_dir) 1064 | 1065 | if stat_resp is None: 1066 | self._mkdir_path(remote_dir) 1067 | elif stat_resp.get("type", "") == "FILE": 1068 | raise cmdException(f"{syntax_str}\n\t{remote_dir}: exists as a file") 1069 | 1070 | if verbose: 1071 | print(f"{cmd} '{local_dir_full}' '{remote_dir}'") 1072 | 1073 | local_dir_len = len(local_dir_full) 1074 | for ROOT, dirs, FILES in os.walk(local_dir_full, topdown=True): 1075 | dirs[:] = [ 1076 | de 1077 | for de in dirs 1078 | if de not in self.excludes and de[0] != "." and "+" not in de 1079 | ] 1080 | FILES[:] = [de for de in FILES if de not in self.excludes and de[0] != "."] 1081 | 1082 | dt = ROOT[local_dir_len:] 1083 | 1084 | remdir = os.path.normpath(remote_dir + "/" + dt) 1085 | 1086 | for d in dirs: 1087 | if verbose: 1088 | print(f"mkdir {remdir}/{d}") 1089 | self._mkdir_path(f"{remdir}/{d}") 1090 | for f in FILES: 1091 | # if not f.isalnum() or '+' in f: 1092 | # continue 1093 | # if verbose: 1094 | # print(f"copy {ROOT} / {f} -> {remdir} / {f}") 1095 | if not self._valid_filename(f): 1096 | print(f"Skipping Invalid filename {f}") 1097 | continue 1098 | try: 1099 | self._put_file(f"{ROOT}/{f}", f"{remdir}/{f}") 1100 | except cmdException as _e: 1101 | # if self.debug: 1102 | # print(f"{remdir}/{f} : {e} : SKIPPING") 1103 | continue 1104 | # t_size += os.path.getsize(f"{ROOT}/{f}") 1105 | 1106 | if check_md5: 1107 | hash1 = calc_file_md5(f"{ROOT}/{f}") 1108 | hash2 = self.flip.rpc_md5sum(f"{remdir}/{f}") 1109 | if hash2 != hash1: 1110 | print("MD5 mismatch: {remdir}/{f}") 1111 | print(f"{hash1} <-> {hash2}") 1112 | 1113 | # print(f"Total: {t_size}") 1114 | 1115 | # this needs code to act like cp/rsync were if source_file ends in a /, 1116 | # the contents of the directory are copied rather than the directory itself 1117 | def do_get_tree(self, cmd, argv): 1118 | """copy directory tree from flipper 1119 | GET-TREE 1120 | If the source_file ends in a /, the contents of 1121 | the directory are copied rather than the directory itself. 1122 | """ 1123 | 1124 | verbose = self.debug or self.verbose 1125 | 1126 | syntax_str = f"Syntax :\n\t{cmd} " 1127 | if len(argv) < 1 or argv[0] in ["?", "help"]: 1128 | raise cmdException(syntax_str) 1129 | 1130 | # add more logic here like with put-tree 1131 | remote_dir = argv.pop(0) 1132 | 1133 | if argv: 1134 | local_dir = argv.pop(0) 1135 | else: 1136 | local_dir = "." 1137 | 1138 | # if verbose: 1139 | # print(f"{cmd} '{remote_dir}' '{local_dir}'") 1140 | 1141 | if not remote_dir.startswith("/"): 1142 | remote_dir_full = os.path.normpath(self.rdir + "/" + remote_dir) 1143 | else: 1144 | remote_dir_full = remote_dir 1145 | 1146 | # if verbose: 1147 | # print(f"'{remote_dir}' -> '{remote_dir_full}'") 1148 | 1149 | stat_resp = self.flip._rpc_stat(remote_dir_full) 1150 | if stat_resp is None or stat_resp.get("type", "") == "FILE": 1151 | raise cmdException( 1152 | f"{syntax_str}\n\t{remote_dir}: is a file, expected directory" 1153 | ) 1154 | 1155 | if os.path.isdir(local_dir) and not remote_dir.endswith("/"): 1156 | local_dir = local_dir + "/" + os.path.basename(remote_dir_full) 1157 | if not os.path.exists(local_dir): 1158 | os.makedirs(local_dir) 1159 | else: 1160 | os.makedirs(local_dir) 1161 | 1162 | if verbose: 1163 | print(f"{cmd} '{remote_dir_full}' '{os.path.abspath(local_dir)}'") 1164 | 1165 | remote_dir_len = len(remote_dir_full) 1166 | for ROOT, dirs, FILES in flipper_tree_walk(remote_dir_full, self.flip): 1167 | dirs[:] = [de for de in dirs if de not in self.excludes] 1168 | 1169 | dt = ROOT[remote_dir_len:] 1170 | locdir = os.path.normpath(local_dir + "/" + dt) 1171 | 1172 | for d in dirs: 1173 | if verbose: 1174 | print(f"mkdir {locdir} / {d}") 1175 | os.makedirs(f"{locdir}/{d}") 1176 | 1177 | for f in FILES: 1178 | # if verbose: 1179 | # print(f"copy {ROOT} / {f} -> {locdir} / {f}") 1180 | self._get_file(f"{ROOT}/{f}", f"{locdir}/{f}") 1181 | 1182 | def do_info(self, _cmd, argv): 1183 | """get Filesystem info 1184 | INFO [filesystem] 1185 | """ 1186 | 1187 | targfs = ["/ext", "/int"] 1188 | 1189 | if len(argv) > 0: 1190 | targ = argv.pop(0) 1191 | 1192 | if not targ.startswith("/"): 1193 | targ = os.path.normpath(self.rdir + "/" + targ) 1194 | targfs = [targ] 1195 | 1196 | for t in targfs: 1197 | info_resp = self.flip.rpc_info(t) 1198 | 1199 | tspace = int(info_resp["totalSpace"]) 1200 | fspace = int(info_resp["freeSpace"]) 1201 | print( 1202 | f"\nfilesystem: {t}\n" 1203 | f" totalSpace: {tspace}\n" 1204 | f" freeSpace: {fspace}\n" 1205 | f" usedspace: {tspace - fspace}" 1206 | ) 1207 | print() 1208 | 1209 | def do_timestamp(self, cmd, argv): 1210 | """get timestamp 1211 | Stat 1212 | """ 1213 | 1214 | if len(argv) == 0 or argv[0] == "?" or len(argv) > 1: 1215 | raise cmdException(f"Syntax :\n\t{cmd} file") 1216 | 1217 | targ = argv.pop(0) 1218 | 1219 | if not targ.startswith("/"): 1220 | targ = os.path.normpath(self.rdir + "/" + targ) 1221 | 1222 | targ = targ.rstrip("/") 1223 | 1224 | if self.debug: 1225 | print(cmd, targ) 1226 | 1227 | timestamp_resp = self.flip.rpc_timestamp(targ) 1228 | 1229 | print(f"{timestamp_resp}") 1230 | 1231 | def do_stat(self, cmd, argv): 1232 | """get info about file or directory 1233 | Stat 1234 | """ 1235 | 1236 | if len(argv) == 0 or argv[0] == "?" or len(argv) > 1: 1237 | raise cmdException(f"Syntax :\n\t{cmd} file") 1238 | 1239 | targ = argv.pop(0) 1240 | 1241 | if not targ.startswith("/"): 1242 | targ = os.path.normpath(self.rdir + "/" + targ) 1243 | 1244 | targ = targ.rstrip("/") 1245 | 1246 | if self.debug: 1247 | print(cmd, targ) 1248 | 1249 | stat_resp = self.flip.rpc_stat(targ) 1250 | 1251 | if self.debug: 1252 | print(f"stat_resp={stat_resp}") 1253 | 1254 | # if stat_resp.get('commandId', 0) != 0: 1255 | # print(f"Error: {stat_resp['commandStatus']}") 1256 | # return 1257 | 1258 | if stat_resp.get("type", "") == "DIR": 1259 | print(f"{targ:<25s}\t DIR") 1260 | else: 1261 | print(f"{targ:<25s}\t{stat_resp['size']:>6d}") 1262 | 1263 | def do_start_session(self, cmd, argv): # pylint: disable=unused-argument 1264 | """(re) start RPC session 1265 | START-SESSION 1266 | """ 1267 | self.flip.start_rpc_session() 1268 | 1269 | def do_stop_session(self, cmd, argv): # pylint: disable=unused-argument 1270 | """stop RPC session 1271 | STOP-SESSION 1272 | """ 1273 | self.flip.rpc_stop_session() 1274 | 1275 | def do_reboot(self, cmd, argv): 1276 | """reboot flipper 1277 | REBOOT [MODE] 1278 | MODE can be 'OS', 'DFU' or 'UPDATE' 1279 | """ 1280 | 1281 | if len(argv) < 1 or argv[0] == "?": 1282 | raise cmdException(f"Syntax :\n\t{cmd} [OS | DFU | UPDATE]") 1283 | 1284 | mode = argv.pop(0) 1285 | 1286 | if mode not in ["OS", "DFU", "UPDATE"]: 1287 | raise cmdException(f"Syntax :\n\t{cmd} [OS | DFU | UPDATE]") 1288 | 1289 | self.flip.rpc_reboot(mode) 1290 | 1291 | # quit if not booting into OS mode 1292 | if mode in ["DFU", "UPDATE"]: 1293 | self.QuitException(f"REBOOT {mode}") 1294 | 1295 | def do_run_app(self, cmd, argv): 1296 | """run application 1297 | RUN-APP 1298 | """ 1299 | if not len(argv): 1300 | raise cmdException(f"Syntax :\n\t{cmd} [args]") 1301 | elif len(argv) == 1: 1302 | args = "" 1303 | else: 1304 | args = " ".join(argv[1:]) 1305 | 1306 | self.flip.rpc_app_start(argv[0], args) 1307 | 1308 | def do_data_exchange(self, cmd, argv): 1309 | """exchange arbitrary data with application 1310 | DATA-XCHANGE [data] 1311 | mode can be 'send' or 'recv' 1312 | data is only applicable for the 'send' mode 1313 | """ 1314 | if not len(argv): 1315 | raise cmdException(f"Syntax :\n\t{cmd} [data]") 1316 | elif argv[0].lower() == "send": 1317 | if len(argv) == 1: 1318 | data = bytes() 1319 | else: 1320 | data = bytes.fromhex("".join(argv[1:])) 1321 | self.flip.rpc_app_data_exchange_send(data) 1322 | elif argv[0].lower() == "recv": 1323 | data = self.flip.rpc_app_data_exchange_recv() 1324 | print(f"Received data: {data.hex(' ', 1)}") 1325 | else: 1326 | raise cmdException(f"Syntax :\n\t{cmd} [data]") 1327 | 1328 | def do_desktop_is_locked(self, cmd, argv): 1329 | print(self.flip.desktop_is_locked()) 1330 | 1331 | def do_desktop_unlock(self, cmd, argv): 1332 | print(self.flip.desktop_unlock()) 1333 | 1334 | def do_untar(self, cmd, argv): 1335 | """extract a tar archive 1336 | UNTAR [output_dir] 1337 | omit the second argument to extract archive to the same directory 1338 | """ 1339 | if len(argv) == 1: 1340 | tar_path = argv[0] 1341 | out_path = os.path.dirname(argv[0]) 1342 | elif len(argv) == 2: 1343 | tar_path = argv[0] 1344 | out_path = argv[1] 1345 | else: 1346 | raise cmdException(f"Syntax:\n\t{cmd} [output_dir]") 1347 | 1348 | self.flip.rpc_storage_untar(tar_path, out_path.rstrip("/")) 1349 | 1350 | 1351 | # 1352 | # Do nothing 1353 | # (syntax check) 1354 | # 1355 | if __name__ == "__main__": 1356 | import __main__ 1357 | 1358 | print(__main__.__file__) 1359 | 1360 | print("syntax ok") 1361 | sys.exit(0) 1362 | -------------------------------------------------------------------------------- /flipperzero_protobuf/flipperCmd/flipperzero_cmd.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """flipper command line app""" 3 | 4 | # import pprint 5 | import argparse 6 | 7 | # import os 8 | # import sys 9 | import shlex 10 | 11 | # from google.protobuf.json_format import MessageToDict 12 | from ..flipper_base import FlipperProtoException # FlipperProtoBase 13 | from ..flipper_base import InputTypeException, Varint32Exception 14 | 15 | # from .flipperCmd import FlipperCMD 16 | from . import FlipperCMD, cmdException 17 | 18 | # from .flipper_storage import FlipperProtoStorage 19 | # from .flipper_proto import FlipperProto 20 | # from .cli_helpers import print_screen, flipper_tree_walk, calc_file_md5 21 | from .cmd_complete import Cmd_Complete 22 | 23 | 24 | def arg_opts(): 25 | """argument parse""" 26 | 27 | parser = argparse.ArgumentParser( 28 | add_help=True, formatter_class=argparse.RawDescriptionHelpFormatter 29 | ) 30 | 31 | parser.add_argument( 32 | "--debug", 33 | dest="debug", 34 | default=0, 35 | help="Increase debug verbosity", 36 | action="store_true", 37 | ) 38 | 39 | parser.add_argument( 40 | "-v", 41 | "--verbose", 42 | dest="verbose", 43 | default=0, 44 | help="Increase debug verbosity", 45 | action="count", 46 | ) 47 | 48 | parser.add_argument( 49 | "-p", "--port", dest="serial_port", default=None, help="Serial Port" 50 | ) 51 | 52 | data_grp = parser.add_mutually_exclusive_group() 53 | 54 | data_grp.add_argument( 55 | "-i", 56 | "--interactive", 57 | dest="interactive", 58 | default=False, 59 | action="store_true", 60 | help="Interactive Mode", 61 | ) 62 | 63 | data_grp.add_argument( 64 | "-c", 65 | "--cmd-file", 66 | dest="cmd_file", 67 | type=argparse.FileType("r", encoding="UTF-8"), 68 | default=None, 69 | help="Command File", 70 | ) 71 | 72 | return parser.parse_known_args() 73 | 74 | 75 | def main() -> None: 76 | """ "Main call start funtion""" 77 | 78 | # global rdir 79 | interactive = False 80 | 81 | arg, u = arg_opts() 82 | 83 | fcmd = FlipperCMD(serial_port=arg.serial_port, verbose=arg.verbose, debug=arg.debug) 84 | # proto = FlipperProto() 85 | 86 | # argv = sys.argv[1:] 87 | 88 | argv = u 89 | 90 | if len(argv) == 0 and arg.cmd_file is None: 91 | print("Entering Interactive Mode") 92 | print(" Device Name:", fcmd.flip.device_info.get("hardware_name", "Unknown")) 93 | print( 94 | f" Firmware: v{fcmd.flip.device_info['firmware_version']} " 95 | f"{fcmd.flip.device_info['firmware_build_date']}" 96 | ) 97 | print(" Serial Port:", fcmd.flip.port()) 98 | print(f" FlipperProto version: {fcmd.flip.version}") 99 | print(" Protobuf Version:", fcmd.flip.rpc_protobuf_version()) 100 | print("\n") 101 | interactive = True 102 | 103 | compl = Cmd_Complete() 104 | compl.setup(volcab=fcmd.get_cmd_keys()) 105 | 106 | # I should rewrite this with the python CMD module framework 107 | lineno = 1 108 | while 1: 109 | try: 110 | if arg.cmd_file: 111 | for line in arg.cmd_file: 112 | if fcmd.verbose or fcmd.debug: 113 | print("cmd=", line) 114 | argv = shlex.split(line, comments=True, posix=True) 115 | if not argv or argv[0][0] == "#": 116 | continue 117 | fcmd.run_comm(argv) 118 | break 119 | 120 | if interactive is True: 121 | # print(f"{fcmd.rdir} flipper> ", end="") 122 | prompt = f"{lineno} {fcmd.rdir} flipper> " 123 | compl.prompt = prompt 124 | argv = shlex.split(input(prompt), comments=True, posix=True) 125 | # if argv is None or len(argv) == 0: 126 | if not argv: 127 | # print() 128 | continue 129 | 130 | lineno += 1 131 | fcmd.run_comm(argv) 132 | 133 | except (EOFError, fcmd.QuitException, KeyboardInterrupt) as _e: 134 | interactive = False 135 | print("") 136 | # print(_e) 137 | break 138 | 139 | except (cmdException, FlipperProtoException) as e: 140 | print("Command Error", e) 141 | if interactive: 142 | continue 143 | break 144 | 145 | except (InputTypeException, ValueError) as e: 146 | print("ValueError", e) 147 | if interactive: 148 | continue 149 | break 150 | 151 | except Varint32Exception as e: 152 | print("Protobuf protocal error", e) 153 | if interactive: 154 | continue 155 | 156 | except IOError as e: 157 | # do we need reconnect code ??? 158 | print("IOError", e) 159 | if interactive: 160 | continue 161 | 162 | # except google.protobuf.message.DecodeError as e: 163 | 164 | except Exception as e: 165 | print(f"Exception: {e}") 166 | raise 167 | 168 | # finally: 169 | 170 | if interactive is not True or arg.cmd_file is not None: 171 | break 172 | 173 | 174 | if __name__ == "__main__": 175 | main() 176 | -------------------------------------------------------------------------------- /flipperzero_protobuf/flipper_app.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | FlipperProto App function Class 4 | """ 5 | 6 | # pylint: disable=line-too-long, no-member 7 | 8 | from .flipper_base import FlipperProtoException 9 | from .flipperzero_protobuf_compiled import application_pb2 10 | 11 | __all__ = ["FlipperProtoApp"] 12 | 13 | 14 | class FlipperProtoApp: 15 | """ 16 | FlipperProto App function Class 17 | """ 18 | 19 | # LockStatus 20 | def rpc_lock_status(self) -> bool: 21 | """Get LockScreen Status 22 | 23 | Returns 24 | ---------- 25 | bool 26 | 27 | Raises 28 | ---------- 29 | FlipperProtoException 30 | 31 | """ 32 | cmd_data = application_pb2.LockStatusRequest() 33 | data = self._rpc_send_and_read_answer(cmd_data, "app_lock_status_request") 34 | 35 | if data.command_status != 0: 36 | raise FlipperProtoException( 37 | self.Status_values_by_number[data.command_status].name 38 | ) 39 | 40 | return data.app_lock_status_response.locked 41 | 42 | # Start 43 | def rpc_app_start(self, name, args) -> None: 44 | """Start/Run application 45 | 46 | Parameters 47 | ---------- 48 | name : str 49 | args : str 50 | 51 | Returns 52 | ---------- 53 | None 54 | 55 | Raises 56 | ---------- 57 | FlipperProtoException 58 | 59 | """ 60 | cmd_data = application_pb2.StartRequest() 61 | cmd_data.name = name 62 | cmd_data.args = args 63 | rep_data = self._rpc_send_and_read_answer(cmd_data, "app_start_request") 64 | if rep_data.command_status != 0: 65 | raise FlipperProtoException( 66 | self.Status_values_by_number[rep_data.command_status].name 67 | ) 68 | 69 | # AppExit 70 | def rpc_app_exit(self) -> None: 71 | """Send exit command to app 72 | 73 | Returns 74 | ---------- 75 | None 76 | 77 | Raises 78 | ---------- 79 | FlipperProtoException 80 | 81 | """ 82 | cmd_data = application_pb2.AppExitRequest() 83 | rep_data = self._rpc_send_and_read_answer(cmd_data, "app_exit_request") 84 | if rep_data.command_status != 0: 85 | raise FlipperProtoException( 86 | self.Status_values_by_number[rep_data.command_status].name 87 | ) 88 | 89 | # AppLoadFile 90 | def rpc_app_load_file(self, path) -> None: 91 | """Send load file command to app. 92 | 93 | Returns 94 | ---------- 95 | None 96 | 97 | Raises 98 | ---------- 99 | FlipperProtoException 100 | 101 | """ 102 | 103 | cmd_data = application_pb2.AppLoadFileRequest() 104 | cmd_data.path = path 105 | rep_data = self._rpc_send_and_read_answer(cmd_data, "app_load_file_request") 106 | if rep_data.command_status != 0: 107 | raise FlipperProtoException( 108 | self.Status_values_by_number[rep_data.command_status].name 109 | ) 110 | 111 | # AppButtonPress 112 | def rpc_app_button_press(self, args) -> None: 113 | """Send button press command to app. 114 | 115 | Returns 116 | ---------- 117 | None 118 | 119 | Raises 120 | ---------- 121 | FlipperProtoException 122 | 123 | """ 124 | 125 | cmd_data = application_pb2.AppButtonPressRequest() 126 | cmd_data.args = args 127 | rep_data = self._rpc_send_and_read_answer(cmd_data, "app_button_press_request") 128 | if rep_data.command_status != 0: 129 | raise FlipperProtoException( 130 | self.Status_values_by_number[rep_data.command_status].name 131 | ) 132 | 133 | # AppButtonRelease 134 | def rpc_app_button_release(self) -> None: 135 | """Send button release command to app 136 | 137 | Returns 138 | ---------- 139 | None 140 | 141 | Raises 142 | ---------- 143 | FlipperProtoException 144 | 145 | """ 146 | cmd_data = application_pb2.AppButtonReleaseRequest() 147 | rep_data = self._rpc_send_and_read_answer( 148 | cmd_data, "app_button_release_request" 149 | ) 150 | if rep_data.command_status != 0: 151 | raise FlipperProtoException( 152 | self.Status_values_by_number[rep_data.command_status].name 153 | ) 154 | 155 | # GetError 156 | def rpc_app_get_error(self) -> tuple[int, str]: 157 | cmd_data = application_pb2.GetErrorRequest() 158 | rep_data = self._rpc_send_and_read_answer(cmd_data, "app_get_error_request") 159 | if rep_data.command_status != 0: 160 | raise FlipperProtoException( 161 | self.Status_values_by_number[rep_data.command_status].name 162 | ) 163 | return ( 164 | rep_data.app_get_error_response.code, 165 | rep_data.app_get_error_response.text, 166 | ) 167 | 168 | # DataExchange (send) 169 | def rpc_app_data_exchange_send(self, data: bytes) -> None: 170 | cmd_data = application_pb2.DataExchangeRequest() 171 | cmd_data.data = data 172 | rep_data = self._rpc_send_and_read_answer(cmd_data, "app_data_exchange_request") 173 | if rep_data.command_status != 0: 174 | raise FlipperProtoException( 175 | self.Status_values_by_number[rep_data.command_status].name 176 | ) 177 | 178 | # DataExchange (receive) 179 | def rpc_app_data_exchange_recv(self) -> bytes: 180 | self._serial.flushInput() 181 | rep_data = self._rpc_read_answer(0) 182 | return rep_data.app_data_exchange_request.data 183 | -------------------------------------------------------------------------------- /flipperzero_protobuf/flipper_base.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """FlipperProto Class init function calls""" 3 | 4 | import os 5 | import sys 6 | from typing import Union 7 | 8 | import serial 9 | import serial.tools.list_ports 10 | from google.protobuf.internal.encoder import _VarintBytes 11 | 12 | from .flipperzero_protobuf_compiled import flipper_pb2 13 | from .version import __version__ 14 | 15 | # pylint: disable=line-too-long, no-member 16 | 17 | 18 | # VERSION = '0.1.20220806' 19 | 20 | __all__ = [ 21 | "Varint32Exception", 22 | "InputTypeException", 23 | "FlipperProtoBase", 24 | "FlipperProtoException", 25 | ] 26 | 27 | 28 | class Varint32Exception(Exception): 29 | """Protobuf protocal communication error Exception""" 30 | 31 | 32 | class InputTypeException(Exception): 33 | """FlipperProto input error Exception""" 34 | 35 | 36 | class FlipperProtoException(Exception): 37 | """FlipperProto callback Exception""" 38 | 39 | def __init__(self, msg): 40 | Exception.__init__(self, msg) 41 | 42 | 43 | class FlipperProtoBase: 44 | """Meta base Class for FlipperProto""" 45 | 46 | def __init__(self, serial_port=None, debug=0) -> None: 47 | # self.info = {} 48 | 49 | self._debug = debug 50 | self._in_session = False # flag if connecion if in RPC or command mode 51 | self.version = __version__ 52 | 53 | self._command_id = 0 54 | if isinstance(serial_port, serial.Serial): 55 | self._serial = serial_port 56 | else: 57 | try: 58 | self._serial = self._open_serial(serial_port) 59 | # print("._get_startup_info") 60 | self.device_info = self._get_startup_info() 61 | self.start_rpc_session() 62 | except serial.serialutil.SerialException as e: 63 | print(f"SerialException: {e}") 64 | sys.exit(0) 65 | 66 | # for easy lookup later 67 | self.Status_values_by_number = flipper_pb2.DESCRIPTOR.enum_types_by_name[ 68 | "CommandStatus" 69 | ].values_by_number 70 | 71 | def port(self) -> str: 72 | """Return serial port""" 73 | if self._serial is None: 74 | return "" 75 | return self._serial.port 76 | 77 | def _get_startup_info(self) -> dict: 78 | """read / record info during startup""" 79 | # cache some data 80 | ret = {} 81 | self._serial.read_until(b">: ") 82 | self._serial.write(b"!\r") 83 | while True: 84 | r = self._serial.readline().decode("utf-8") 85 | 86 | if r.startswith(">: "): 87 | break 88 | if r.startswith("\r\n"): 89 | break 90 | 91 | if len(r) > 5: 92 | k, v = r.split(":", maxsplit=1) 93 | ret[k.strip()] = v.strip() 94 | 95 | return ret 96 | 97 | # COM4: USB Serial Device (COM4) [USB VID:PID=0483:5740 SER=FLIP_UNYANA LOCATION=1-3:x.0] 98 | # /dev/cu.usbmodemflip_Unyana1: Flipper Unyana [USB VID:PID=0483:5740 SER=flip_Unyana LOCATION=20-2] 99 | def _find_port(self) -> Union[str, None]: # -> str | None: 100 | """find serial device""" 101 | 102 | ports = serial.tools.list_ports.comports() 103 | for port, desc, hwid in ports: 104 | if self._debug: 105 | print(f"{port}: {desc} [{hwid}]") 106 | 107 | a = hwid.split() 108 | if "VID:PID=0483:5740" in a: 109 | return port 110 | 111 | # a[2].startswith("SER=flip") 112 | # if desc.startswith("Flipper") or desc.startswith("Rogue"): 113 | # return port 114 | 115 | return None 116 | 117 | def _open_serial(self, dev=None) -> serial.Serial: 118 | """open serial device""" 119 | 120 | serial_dev = dev or self._find_port() 121 | 122 | if serial_dev is None: 123 | print("can not find Flipper serial dev") 124 | sys.exit(0) 125 | 126 | if not os.path.exists(serial_dev): 127 | print(f"can not open {serial_dev}") 128 | sys.exit(0) 129 | 130 | if self._debug: 131 | print(f"Using port {serial_dev}") 132 | 133 | # open serial port 134 | # serial.serialutil.SerialException 135 | # flipper = serial.Serial(sys.argv[1], timeout=1) 136 | flipper = serial.Serial(serial_dev, timeout=1) 137 | flipper.baudrate = 230400 138 | flipper.flushOutput() 139 | flipper.flushInput() 140 | 141 | # disable timeout 142 | flipper.timeout = None 143 | 144 | # wait for prompt 145 | # flipper.read_until(b'>: ') 146 | 147 | return flipper 148 | 149 | def send_cmd(self, cmd_str) -> None: 150 | """send non rpc command to flipper""" 151 | if self._in_session: 152 | raise FlipperProtoException("rpc_session is active") 153 | 154 | self._serial.read_until(b">: ") 155 | self._serial.write(cmd_str + "\r") 156 | 157 | while True: 158 | r = self._serial.readline().decode("utf-8") 159 | print(r) 160 | 161 | if r.startswith(">: "): 162 | break 163 | 164 | def start_rpc_session(self) -> None: 165 | """start rpc session""" 166 | # wait for prompt 167 | self._serial.read_until(b">: ") 168 | 169 | # send command and skip answer 170 | self._serial.write(b"start_rpc_session\r") 171 | self._serial.read_until(b"\n") 172 | self._in_session = True 173 | 174 | def _read_varint_32(self) -> int: 175 | """Read varint from serial port""" 176 | MASK = (1 << 32) - 1 177 | 178 | result = 0 179 | shift = 0 180 | while 1: 181 | b = int.from_bytes( 182 | self._serial.read(size=1), byteorder="little", signed=False 183 | ) 184 | result |= (b & 0x7F) << shift 185 | 186 | if not b & 0x80: 187 | result &= MASK 188 | result = int(result) 189 | return result 190 | shift += 7 191 | if shift >= 64: 192 | raise Varint32Exception("Too many bytes when decoding varint.") 193 | 194 | def _get_command_id(self) -> int: 195 | """Increment and get command id""" 196 | self._command_id += 1 197 | result = self._command_id 198 | return result 199 | 200 | def _rpc_send(self, cmd_data, cmd_name, has_next=None, command_id=None) -> None: 201 | """Send command""" 202 | 203 | if self._in_session is False: 204 | raise FlipperProtoException("rpc_session is not active") 205 | 206 | flipper_message = flipper_pb2.Main() 207 | if command_id is None: 208 | flipper_message.command_id = self._get_command_id() 209 | else: 210 | flipper_message.command_id = command_id 211 | 212 | flipper_message.command_status = flipper_pb2.CommandStatus.Value("OK") 213 | if has_next: 214 | flipper_message.has_next = has_next 215 | getattr(flipper_message, cmd_name).CopyFrom(cmd_data) 216 | data = bytearray( 217 | _VarintBytes(flipper_message.ByteSize()) 218 | + flipper_message.SerializeToString() 219 | ) 220 | self._serial.write(data) 221 | 222 | def _rpc_send_and_read_answer( 223 | self, cmd_data, cmd_name, has_next=False, command_id=None 224 | ) -> flipper_pb2.Main: 225 | """Send command and read answer""" 226 | self._rpc_send(cmd_data, cmd_name, has_next=has_next, command_id=command_id) 227 | return self._rpc_read_answer() 228 | 229 | def _rpc_read_answer(self, command_id=None) -> flipper_pb2.Main: 230 | """Read answer from serial port and filter by command id""" 231 | # message->DebugString() 232 | 233 | if self._in_session is False: 234 | raise FlipperProtoException("rpc_session is not active") 235 | 236 | if command_id is None: 237 | command_id = self._command_id 238 | 239 | while True: 240 | data = self._rpc_read_any() 241 | if data.command_id == command_id: 242 | break 243 | return data 244 | 245 | def _rpc_read_any(self) -> flipper_pb2.Main: 246 | """Read answer from serial port""" 247 | length = self._read_varint_32() 248 | data = flipper_pb2.Main() 249 | data.ParseFromString(self._serial.read(size=length)) 250 | return data 251 | -------------------------------------------------------------------------------- /flipperzero_protobuf/flipper_desktop.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | FlipperProtoDesktop system related function Class 4 | """ 5 | 6 | # import sys 7 | # import os 8 | import datetime 9 | 10 | from google.protobuf.json_format import MessageToDict 11 | 12 | from .flipper_base import FlipperProtoException, InputTypeException 13 | from .flipperzero_protobuf_compiled import flipper_pb2, desktop_pb2 14 | 15 | # import pprint 16 | 17 | # from nis import match 18 | # from numpy import mat 19 | 20 | # pylint: disable=line-too-long, no-member 21 | 22 | 23 | __all__ = ["FlipperProtoDesktop"] 24 | 25 | 26 | class FlipperProtoDesktop: 27 | def desktop_is_locked(self) -> None: 28 | cmd_data = desktop_pb2.IsLockedRequest() 29 | rep_data = self._rpc_send_and_read_answer(cmd_data, "desktop_is_locked_request") 30 | return rep_data.command_status 31 | 32 | def desktop_unlock(self) -> None: 33 | cmd_data = desktop_pb2.UnlockRequest() 34 | rep_data = self._rpc_send_and_read_answer(cmd_data, "desktop_unlock_request") 35 | return rep_data.command_status 36 | -------------------------------------------------------------------------------- /flipperzero_protobuf/flipper_gpio.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | FlipperProto GPIO function Class 4 | """ 5 | 6 | # import sys 7 | # import os 8 | 9 | # from nis import match 10 | # from google.protobuf.internal.encoder import _VarintBytes 11 | # from numpy import mat 12 | 13 | # pylint: disable=line-too-long, no-member 14 | 15 | from .flipper_base import FlipperProtoException, InputTypeException 16 | from .flipperzero_protobuf_compiled import gpio_pb2 17 | 18 | __all__ = ["FlipperProtoGpio"] 19 | 20 | 21 | class FlipperProtoGpio: 22 | """ 23 | 24 | Methods 25 | ------- 26 | cmd_gpio_get_pin_mode(pin): 27 | get GPIO pin mode 28 | 29 | cmd_gpio_set_pin_mode(pin, mode): 30 | set GPIO pin mode 31 | 32 | cmd_gpio_write_pin(pin, value): 33 | set GPIO pin 34 | 35 | cmd_gpio_read_pin(pin): 36 | query GPIO pin 37 | 38 | cmd_gpio_set_input_pull(pin, pull_mode): 39 | Set GPIO pill Input 40 | 41 | GpioPin ID: 42 | 0 = 'PC0' 43 | 1 = 'PC1' 44 | 2 = 'PC3' 45 | 3 = 'PB2' 46 | 4 = 'PB3' 47 | 5 = 'PA4' 48 | 6 = 'PA6' 49 | 7 = 'PA7' 50 | 51 | GpioInputPull 52 | 0 = 'NO' 53 | 1 = 'UP' 54 | 2 = 'DOWN' 55 | 56 | GpioPinMode 57 | 0 = 'OUTPUT' 58 | 1 = 'INPUT' 59 | """ 60 | 61 | # GetPinMode 62 | def rpc_gpio_get_pin_mode(self, pin) -> str: 63 | """get GPIO pin mode 64 | 65 | Parameters 66 | ---------- 67 | pin : int or str 68 | 0 = 'PC0' 69 | 1 = 'PC1' 70 | 2 = 'PC3' 71 | 3 = 'PB2' 72 | 4 = 'PB3' 73 | 5 = 'PA4' 74 | 6 = 'PA6' 75 | 7 = 'PA7' 76 | 77 | Returns: 78 | ---------- 79 | str 80 | 'OUTPUT' 81 | 'INPUT' 82 | 83 | Raises 84 | ---------- 85 | InputTypeException 86 | FlipperProtoException 87 | 88 | """ 89 | 90 | cmd_data = gpio_pb2.GetPinMode() 91 | if pin not in ["PC0", "PC1", "PC3", "PB2", "PB3", "PA4", "PA6", "PA7"]: 92 | raise InputTypeException("Invalid pin") 93 | 94 | if isinstance(pin, int): 95 | cmd_data.pin = pin 96 | else: 97 | cmd_data.pin = getattr(gpio_pb2, pin) 98 | 99 | # if _debug: 100 | # print(f"gpio_pb2.GetPinMode pin={pin} cmd_data.pin={cmd_data.pin}") 101 | 102 | rep_data = self._rpc_send_and_read_answer(cmd_data, "gpio_get_pin_mode") 103 | 104 | # gpio_pb2.DESCRIPTOR.enum_types_by_name['GpioPinMode'].values_by_number[0].name 105 | if rep_data.command_status != 0: 106 | raise FlipperProtoException( 107 | f"{self.Status_values_by_number[rep_data.command_status].name} pin={pin}" 108 | ) 109 | 110 | # return rep_data.gpio_get_pin_mode_response.mode 111 | return ( 112 | gpio_pb2.DESCRIPTOR.enum_types_by_name["GpioPinMode"] 113 | .values_by_number[rep_data.gpio_get_pin_mode_response.mode] 114 | .name 115 | ) 116 | 117 | # SetPinMode 118 | def rpc_gpio_set_pin_mode(self, pin, mode) -> None: 119 | """set GPIO pin mode 120 | 121 | Parameters 122 | ---------- 123 | pin : int or str 124 | 0 = 'PC0' 125 | 1 = 'PC1' 126 | 2 = 'PC3' 127 | 3 = 'PB2' 128 | 4 = 'PB3' 129 | 5 = 'PA4' 130 | 6 = 'PA6' 131 | 7 = 'PA7' 132 | 133 | mode : str 134 | 0 = 'OUTPUT' 135 | 1 = 'INPUT' 136 | 137 | Returns: 138 | ---------- 139 | None 140 | 141 | Raises 142 | ---------- 143 | InputTypeException 144 | FlipperProtoException 145 | 146 | """ 147 | 148 | cmd_data = gpio_pb2.SetPinMode() 149 | 150 | if isinstance(pin, int): 151 | cmd_data.pin = pin 152 | else: 153 | if pin not in ["PC0", "PC1", "PC3", "PB2", "PB3", "PA4", "PA6", "PA7"]: 154 | raise InputTypeException("Invalid pin") 155 | cmd_data.pin = getattr(gpio_pb2, pin) 156 | 157 | if mode not in ["OUTPUT", "INPUT"]: 158 | raise InputTypeException("Invalid mode") 159 | cmd_data.mode = getattr(gpio_pb2, mode) 160 | 161 | rep_data = self._rpc_send_and_read_answer(cmd_data, "gpio_set_pin_mode") 162 | 163 | if rep_data.command_status != 0: 164 | raise FlipperProtoException( 165 | f"{self.Status_values_by_number[rep_data.command_status].name} pin={pin} mode={mode}" 166 | ) 167 | 168 | # WritePin 169 | def rpc_gpio_write_pin(self, pin, value) -> None: 170 | """write GPIO pin 171 | 172 | Parameters 173 | ---------- 174 | pin : int or str 175 | 0 = 'PC0' 176 | 1 = 'PC1' 177 | 2 = 'PC3' 178 | 3 = 'PB2' 179 | 4 = 'PB3' 180 | 5 = 'PA4' 181 | 6 = 'PA6' 182 | 7 = 'PA7' 183 | 184 | value : int 185 | 186 | Returns: 187 | ---------- 188 | None 189 | 190 | Raises 191 | ---------- 192 | InputTypeException 193 | FlipperProtoException 194 | 195 | """ 196 | 197 | cmd_data = gpio_pb2.WritePin() 198 | 199 | if isinstance(pin, int): 200 | cmd_data.pin = pin 201 | else: 202 | if pin not in ["PC0", "PC1", "PC3", "PB2", "PB3", "PA4", "PA6", "PA7"]: 203 | raise InputTypeException("Invalid pin") 204 | cmd_data.pin = getattr(gpio_pb2, pin) 205 | 206 | cmd_data.value = value 207 | 208 | rep_data = self._rpc_send_and_read_answer(cmd_data, "gpio_write_pin") 209 | 210 | if rep_data.command_status != 0: 211 | raise FlipperProtoException( 212 | f"{self.Status_values_by_number[rep_data.command_status].name} pin={pin} value={value}" 213 | ) 214 | 215 | # ReadPin 216 | def rpc_gpio_read_pin(self, pin) -> int: 217 | """query GPIO pin 218 | 219 | Parameters 220 | ---------- 221 | pin : int or str 222 | 0 = 'PC0' 223 | 1 = 'PC1' 224 | 2 = 'PC3' 225 | 3 = 'PB2' 226 | 4 = 'PB3' 227 | 5 = 'PA4' 228 | 6 = 'PA6' 229 | 7 = 'PA7' 230 | 231 | Returns: 232 | ---------- 233 | int 234 | pin value 235 | 236 | Raises 237 | ---------- 238 | InputTypeException 239 | FlipperProtoException 240 | 241 | """ 242 | 243 | cmd_data = gpio_pb2.ReadPin() 244 | 245 | if isinstance(pin, int): 246 | cmd_data.pin = pin 247 | else: 248 | if pin not in ["PC0", "PC1", "PC3", "PB2", "PB3", "PA4", "PA6", "PA7"]: 249 | raise InputTypeException("Invalid pin") 250 | cmd_data.pin = getattr(gpio_pb2, pin) 251 | 252 | rep_data = self._rpc_send_and_read_answer(cmd_data, "gpio_read_pin") 253 | 254 | if rep_data.command_status != 0: 255 | raise FlipperProtoException( 256 | f"{self.Status_values_by_number[rep_data.command_status].name} pin={pin}" 257 | ) 258 | 259 | return rep_data.read_pin_response.value 260 | 261 | # SetInputPull 262 | def rpc_gpio_set_input_pull(self, pin, pull_mode) -> None: 263 | """Set GPIO pill Input 264 | 265 | Parameters 266 | ---------- 267 | pin : int or str 268 | 0 = 'PC0' 269 | 1 = 'PC1' 270 | 2 = 'PC3' 271 | 3 = 'PB2' 272 | 4 = 'PB3' 273 | 5 = 'PA4' 274 | 6 = 'PA6' 275 | 7 = 'PA7' 276 | 277 | pull_mode : str 278 | 0 = 'NO' 279 | 1 = 'UP' 280 | 2 = 'DOWN' 281 | 282 | Returns: 283 | ---------- 284 | None 285 | 286 | Raises 287 | ---------- 288 | InputTypeException 289 | FlipperProtoException 290 | 291 | """ 292 | 293 | cmd_data = gpio_pb2.SetInputPull() 294 | if isinstance(pin, int): 295 | cmd_data.pin = pin 296 | else: 297 | if pin not in ["PC0", "PC1", "PC3", "PB2", "PB3", "PA4", "PA6", "PA7"]: 298 | raise InputTypeException("Invalid pin") 299 | cmd_data.pin = getattr(gpio_pb2, pin) 300 | 301 | if pull_mode not in ["NO", "UP", "DOWN"]: 302 | raise InputTypeException("Invalid pull_mode") 303 | cmd_data.pull_mode = getattr(gpio_pb2, pull_mode) 304 | 305 | rep_data = self._rpc_send_and_read_answer(cmd_data, "gpio_set_input_pull") 306 | 307 | if rep_data.command_status != 0: 308 | raise FlipperProtoException( 309 | f"{self.Status_values_by_number[rep_data.command_status].name} pin={pin} pull_mode={pull_mode}" 310 | ) 311 | -------------------------------------------------------------------------------- /flipperzero_protobuf/flipper_gui.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | FlipperProto Input UI function Class 4 | """ 5 | 6 | from .flipper_base import FlipperProtoException, InputTypeException 7 | from .flipperzero_protobuf_compiled import gui_pb2 8 | 9 | # from .flipper_base import * 10 | 11 | # pylint: disable=line-too-long, no-member 12 | 13 | __all__ = ["FlipperProtoGui"] 14 | 15 | """ 16 | InputKey 17 | 0 UP 18 | 1 DOWN 19 | 2 RIGHT 20 | 3 LEFT 21 | 4 OK 22 | 23 | InputType 24 | 1 RELEASE 25 | 2 SHORT 26 | 3 LONG 27 | 4 REPEAT 28 | """ 29 | 30 | 31 | class FlipperProtoGui: 32 | """ 33 | FlipperProto Input UI function Class 34 | """ 35 | 36 | # StartVirtualDisplay 37 | def rpc_start_virtual_display(self, data) -> None: 38 | """Start Virtual Display 39 | 40 | Parameters 41 | ---------- 42 | data : bytes 43 | 44 | Returns 45 | ------- 46 | None 47 | 48 | Raises 49 | ---------- 50 | FlipperProtoException 51 | 52 | """ 53 | 54 | cmd_data = gui_pb2.StartVirtualDisplayRequest() 55 | cmd_data.first_frame.data = data 56 | rep_data = self._rpc_send_and_read_answer( 57 | cmd_data, "gui_start_virtual_display_request" 58 | ) 59 | if rep_data.command_status != 0: 60 | raise FlipperProtoException( 61 | self.Status_values_by_number[rep_data.command_status].name 62 | ) 63 | 64 | # SendVirtualDisplayFrame 65 | def rpc_send_virtual_display_frame(self, data) -> None: 66 | """Send Frame to Virtual Display 67 | 68 | Parameters 69 | ---------- 70 | data : bytes 71 | 72 | Returns 73 | ------- 74 | None 75 | 76 | """ 77 | cmd_data = gui_pb2.ScreenFrame() 78 | cmd_data.data = data 79 | self._rpc_send(cmd_data, "gui_screen_frame") 80 | 81 | # StopVirtualDisplay 82 | def rpc_stop_virtual_display(self) -> None: 83 | """Stop Virtual Display 84 | 85 | Parameters 86 | ---------- 87 | None 88 | 89 | Returns 90 | ------- 91 | None 92 | 93 | Raises 94 | ---------- 95 | FlipperProtoException 96 | 97 | """ 98 | 99 | cmd_data = gui_pb2.StopVirtualDisplayRequest() 100 | rep_data = self._rpc_send_and_read_answer( 101 | cmd_data, "gui_stop_virtual_display_request" 102 | ) 103 | if rep_data.command_status != 0: 104 | raise FlipperProtoException( 105 | self.Status_values_by_number[rep_data.command_status].name 106 | ) 107 | 108 | # StartScreenStream 109 | def rpc_gui_start_screen_stream(self) -> None: 110 | """Start screen stream 111 | 112 | Parameters 113 | ---------- 114 | None 115 | 116 | Returns 117 | ------- 118 | None 119 | 120 | Raises 121 | ---------- 122 | FlipperProtoException 123 | 124 | """ 125 | 126 | cmd_data = gui_pb2.StartScreenStreamRequest() 127 | rep_data = self._rpc_send_and_read_answer( 128 | cmd_data, "gui_start_screen_stream_request" 129 | ) 130 | if rep_data.command_status != 0: 131 | raise FlipperProtoException( 132 | self.Status_values_by_number[rep_data.command_status].name 133 | ) 134 | 135 | # StopScreenStream 136 | def rpc_gui_stop_screen_stream(self) -> None: 137 | """Stop screen stream 138 | 139 | Parameters 140 | ---------- 141 | None 142 | 143 | Returns 144 | ------- 145 | None 146 | 147 | Raises 148 | ---------- 149 | FlipperProtoException 150 | 151 | """ 152 | 153 | cmd_data = gui_pb2.StopScreenStreamRequest() 154 | rep_data = self._rpc_send_and_read_answer( 155 | cmd_data, "gui_stop_screen_stream_request" 156 | ) 157 | 158 | if rep_data.command_status != 0: 159 | raise FlipperProtoException( 160 | self.Status_values_by_number[rep_data.command_status].name 161 | ) 162 | 163 | def rpc_gui_snapshot_screen(self) -> bytes: 164 | """Snapshot screen 165 | 166 | Parameters 167 | ---------- 168 | None 169 | 170 | Returns 171 | ------- 172 | bytes 173 | 174 | Raises 175 | ---------- 176 | FlipperProtoException 177 | 178 | """ 179 | 180 | self.rpc_gui_start_screen_stream() 181 | data = self._rpc_read_answer(0) 182 | self.rpc_gui_stop_screen_stream() 183 | return data.gui_screen_frame.data 184 | 185 | # SendInputEvent 186 | def rpc_gui_send_input_event_request(self, key, itype) -> None: 187 | """Send Input Event Request Key 188 | 189 | Parameters 190 | ---------- 191 | key : str 192 | 'UP', 'DOWN', 'RIGHT', 'LEFT', 'OK' 193 | itype : str 194 | 'PRESS', 'RELEASE', 'SHORT', 'LONG', 'REPEAT' 195 | 196 | Returns 197 | ------- 198 | None 199 | 200 | Raises 201 | ---------- 202 | FlipperProtoException 203 | 204 | """ 205 | 206 | cmd_data = gui_pb2.SendInputEventRequest() 207 | cmd_data.key = getattr(gui_pb2, key) 208 | cmd_data.type = getattr(gui_pb2, itype) 209 | rep_data = self._rpc_send_and_read_answer( 210 | cmd_data, "gui_send_input_event_request" 211 | ) 212 | 213 | if rep_data.command_status != 0: 214 | raise FlipperProtoException( 215 | f"{self.Status_values_by_number[rep_data.command_status].name} {key}, {itype}" 216 | ) 217 | 218 | def rpc_gui_send_input(self, key_arg) -> None: 219 | """Send Input Event Request Type 220 | 221 | Parameters 222 | ---------- 223 | key_arg : tuple 224 | tuple = (InputKey, InputType) 225 | valid InputKeykey values: 'UP', 'DOWN', 'RIGHT', 'LEFT', 'OK' 226 | valid InputType values: 'PRESS', 'RELEASE', 'SHORT', 'LONG', 'REPEAT' 227 | 228 | Returns 229 | ------- 230 | None 231 | 232 | Raises 233 | ---------- 234 | FlipperProtoException 235 | InputTypeException 236 | 237 | """ 238 | itype, ikey = key_arg.split(" ") 239 | 240 | # if itype != 'SHORT' and itype != 'LONG': 241 | if itype not in ["SHORT", "LONG"]: 242 | raise InputTypeException("Incorrect type") 243 | 244 | # if ikey != 'UP' and ikey != 'DOWN' and ikey != 'LEFT' and ikey != 'RIGHT' and ikey != 'OK' and ikey != 'BACK': 245 | if ikey not in ["UP", "DOWN", "LEFT", "RIGHT", "OK", "BACK"]: 246 | raise InputTypeException("Incorrect key") 247 | 248 | self.rpc_gui_send_input_event_request(ikey, "PRESS") 249 | self.rpc_gui_send_input_event_request(ikey, itype) 250 | self.rpc_gui_send_input_event_request(ikey, "RELEASE") 251 | -------------------------------------------------------------------------------- /flipperzero_protobuf/flipper_property.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | FlipperProto property related function Class 4 | """ 5 | 6 | # import sys 7 | # import os 8 | import datetime 9 | 10 | from google.protobuf.json_format import MessageToDict 11 | 12 | from .flipper_base import FlipperProtoException, InputTypeException 13 | from .flipperzero_protobuf_compiled import flipper_pb2, property_pb2 14 | 15 | # import pprint 16 | 17 | # from nis import match 18 | # from numpy import mat 19 | 20 | # pylint: disable=line-too-long, no-member 21 | 22 | 23 | __all__ = ["FlipperProtoProperty"] 24 | 25 | 26 | class FlipperProtoProperty: 27 | """FlipperProto property function Class""" 28 | 29 | # CommonInfo 30 | def rpc_property_get(self, key: str) -> list[tuple[str, str]]: 31 | """Property get 32 | 33 | Return 34 | ---------- 35 | list[tuple[key, value : str]] 36 | 37 | Raises 38 | ---------- 39 | FlipperProtoException 40 | 41 | """ 42 | 43 | cmd_data = property_pb2.GetRequest() 44 | cmd_data.key = key 45 | 46 | rep_data = self._rpc_send_and_read_answer(cmd_data, "property_get_request") 47 | 48 | if rep_data.command_status != 0: 49 | raise FlipperProtoException( 50 | self.Status_values_by_number[rep_data.command_status].name 51 | ) 52 | 53 | ret = [] 54 | 55 | while rep_data.has_next: 56 | ret.append( 57 | ( 58 | rep_data.property_get_response.key, 59 | rep_data.property_get_response.value, 60 | ) 61 | ) 62 | 63 | rep_data = self._rpc_read_answer() 64 | 65 | return ret 66 | -------------------------------------------------------------------------------- /flipperzero_protobuf/flipper_proto.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Meta command class for FlipperProto""" 3 | 4 | # pylint: disable=too-few-public-methods 5 | 6 | 7 | from .flipper_app import FlipperProtoApp 8 | from .flipper_base import FlipperProtoBase 9 | from .flipper_gpio import FlipperProtoGpio 10 | from .flipper_desktop import FlipperProtoDesktop 11 | from .flipper_gui import FlipperProtoGui 12 | from .flipper_property import FlipperProtoProperty 13 | from .flipper_storage import FlipperProtoStorage 14 | from .flipper_sys import FlipperProtoSys 15 | 16 | # from .flipperzero_protobuf_compiled import flipper_pb2, system_pb2, gui_pb2, gpio_pb2 17 | 18 | __all__ = ["FlipperProto"] 19 | 20 | 21 | class FlipperProto( 22 | FlipperProtoBase, 23 | FlipperProtoDesktop, 24 | FlipperProtoSys, 25 | FlipperProtoGpio, 26 | FlipperProtoApp, 27 | FlipperProtoGui, 28 | FlipperProtoStorage, 29 | FlipperProtoProperty, 30 | ): 31 | """ 32 | Meta command class 33 | """ 34 | 35 | # pylint: disable=unnecessary-pass 36 | pass 37 | -------------------------------------------------------------------------------- /flipperzero_protobuf/flipper_storage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | FlipperProto Storage File I/O function Class 4 | """ 5 | 6 | # import hashlib 7 | from typing import Union 8 | 9 | from google.protobuf.json_format import MessageToDict 10 | 11 | from .flipper_base import FlipperProtoException 12 | from .flipperzero_protobuf_compiled import storage_pb2 13 | 14 | # pylint: disable=line-too-long, no-member 15 | 16 | __all__ = ["FlipperProtoStorage"] 17 | 18 | 19 | class FlipperProtoStorage: 20 | """ 21 | FlipperProto Storage File I/O function Class 22 | """ 23 | 24 | # BackupRestore 25 | # BackupCreate 26 | 27 | def rpc_backup_create(self, archive_path=None) -> None: 28 | """Create Backup 29 | 30 | Parameters 31 | ---------- 32 | archive_path : str 33 | path to archive_path 34 | 35 | Returns 36 | ------- 37 | None 38 | 39 | Raises 40 | ---------- 41 | FlipperProtoException 42 | 43 | """ 44 | cmd_data = storage_pb2.BackupCreateRequest() 45 | cmd_data.archive_path = archive_path 46 | 47 | rep_data = self._rpc_send_and_read_answer( 48 | cmd_data, "storage_backup_create_request" 49 | ) 50 | 51 | if rep_data.command_status != 0: 52 | raise FlipperProtoException( 53 | f"{rep_data.command_status} : {self.Status_values_by_number[rep_data.command_status].name} archive_path={archive_path}" 54 | ) 55 | 56 | def rpc_backup_restore(self, archive_path=None) -> None: 57 | """Backup Restore 58 | 59 | Parameters 60 | ---------- 61 | archive_path : str 62 | path to archive_path 63 | 64 | Returns 65 | ------- 66 | None 67 | 68 | Raises 69 | ---------- 70 | FlipperProtoException 71 | 72 | """ 73 | cmd_data = storage_pb2.BackupRestoreRequest() 74 | cmd_data.archive_path = archive_path 75 | 76 | rep_data = self._rpc_send_and_read_answer( 77 | cmd_data, "storage_backup_restore_request" 78 | ) 79 | 80 | if rep_data.command_status != 0: 81 | raise FlipperProtoException( 82 | f"{rep_data.command_status}: {self.Status_values_by_number[rep_data.command_status].name} archive_path={archive_path}" 83 | ) 84 | 85 | def rpc_read(self, path=None) -> bytes: 86 | """read file from flipperzero device 87 | 88 | Parameters 89 | ---------- 90 | path : str 91 | path to file on flipper device 92 | paths must be full path 93 | paths must not have trailing '/' 94 | 95 | Returns 96 | ------- 97 | bytes 98 | 99 | Raises 100 | ---------- 101 | FlipperProtoException 102 | 103 | """ 104 | 105 | storage_response = [] 106 | cmd_data = storage_pb2.ReadRequest() 107 | cmd_data.path = path 108 | 109 | rep_data = self._rpc_send_and_read_answer(cmd_data, "storage_read_request") 110 | 111 | if rep_data.command_status != 0: 112 | raise FlipperProtoException( 113 | f"{rep_data.command_status} : {self.Status_values_by_number[rep_data.command_status].name} path={path}" 114 | ) 115 | 116 | storage_response.append(rep_data.storage_read_response.file.data) 117 | 118 | # j = 0 119 | while rep_data.has_next: 120 | # j += 1 121 | rep_data = self._rpc_read_answer() 122 | storage_response.append(rep_data.storage_read_response.file.data) 123 | 124 | return b"".join(storage_response) 125 | 126 | def rpc_write(self, path=None, data="") -> None: 127 | """write file from flipperzero device 128 | 129 | Parameters 130 | ---------- 131 | path : str 132 | path to file on flipper device 133 | path must be full path 134 | path must not have trailing '/' 135 | data : bytes 136 | data to write 137 | 138 | Raises 139 | ---------- 140 | FlipperProtoException 141 | 142 | """ 143 | if self._debug: 144 | print(f"\ncmd_write path={path}") 145 | cmd_data = storage_pb2.WriteRequest() 146 | cmd_data.path = path 147 | 148 | if isinstance(data, str): 149 | data = data.encode() 150 | 151 | chunk_size = 512 152 | data_len = len(data) 153 | command_id = self._get_command_id() 154 | for chunk in range(0, data_len, chunk_size): 155 | chunk_data = data[chunk : chunk + chunk_size] 156 | 157 | cmd_data.file.data = chunk_data 158 | 159 | if (chunk + chunk_size) < data_len: 160 | self._rpc_send( 161 | cmd_data, 162 | "storage_write_request", 163 | has_next=True, 164 | command_id=command_id, 165 | ) 166 | else: 167 | self._rpc_send( 168 | cmd_data, 169 | "storage_write_request", 170 | has_next=False, 171 | command_id=command_id, 172 | ) 173 | break 174 | 175 | rep_data = self._rpc_read_answer() 176 | if rep_data.command_status != 0: 177 | raise FlipperProtoException( 178 | f"{rep_data.command_status} : {self.Status_values_by_number[rep_data.command_status].name} path={path}" 179 | ) 180 | 181 | def rpc_info(self, path=None) -> dict: 182 | """get filesystem info 183 | 184 | Parameters 185 | ---------- 186 | path : str 187 | path to filesystem 188 | path must be full path 189 | path must not have trailing '/' 190 | 191 | Returns: 192 | ---------- 193 | dict 194 | 195 | Raises 196 | ---------- 197 | FlipperProtoException 198 | 199 | """ 200 | 201 | if path is None: 202 | raise ValueError("path can not be None") 203 | 204 | if self._debug: 205 | print(f"\ncmd_info path={path}") 206 | 207 | cmd_data = storage_pb2.InfoRequest() 208 | cmd_data.path = path 209 | 210 | rep_data = self._rpc_send_and_read_answer(cmd_data, "storage_info_request") 211 | 212 | if rep_data.command_status != 0: 213 | raise FlipperProtoException( 214 | f"{rep_data.command_status} : {self.Status_values_by_number[rep_data.command_status].name} path={path}" 215 | ) 216 | 217 | return MessageToDict(message=rep_data.storage_info_response) 218 | 219 | def _rpc_stat(self, path=None) -> Union[dict, None]: # -> dict | None: 220 | """ 221 | stat without FlipperProtoException 222 | """ 223 | 224 | # print(f"_rpc_stat path={path}") 225 | 226 | cmd_data = storage_pb2.StatRequest() 227 | cmd_data.path = path 228 | 229 | rep_data = self._rpc_send_and_read_answer(cmd_data, "storage_stat_request") 230 | 231 | if rep_data.command_status != 0: 232 | return None 233 | 234 | return MessageToDict( 235 | message=rep_data.storage_stat_response.file, 236 | including_default_value_fields=True, 237 | ) 238 | 239 | def rpc_timestamp(self, path=None) -> int: 240 | """get info or file or directory file from flipperzero device 241 | 242 | Parameters 243 | ---------- 244 | path : str 245 | path to file on flipper device 246 | path must be full path 247 | path must not have trailing '/' 248 | 249 | Raises 250 | ---------- 251 | FlipperProtoException 252 | 253 | """ 254 | if path is None: 255 | raise ValueError("path can not be None") 256 | 257 | cmd_data = storage_pb2.TimestampRequest() 258 | cmd_data.path = path 259 | 260 | rep_data = self._rpc_send_and_read_answer(cmd_data, "storage_timestamp_request") 261 | 262 | if rep_data.command_status != 0: 263 | raise FlipperProtoException( 264 | f"{rep_data.command_status}: {self.Status_values_by_number[rep_data.command_status].name} path={path}" 265 | ) 266 | 267 | return rep_data.storage_timestamp_response.timestamp 268 | 269 | def rpc_stat(self, path=None) -> dict: 270 | """get info or file or directory file from flipperzero device 271 | 272 | Parameters 273 | ---------- 274 | path : str 275 | path to file on flipper device 276 | path must be full path 277 | path must not have trailing '/' 278 | 279 | Raises 280 | ---------- 281 | FlipperProtoException 282 | 283 | """ 284 | if path is None: 285 | raise ValueError("path can not be None") 286 | 287 | cmd_data = storage_pb2.StatRequest() 288 | cmd_data.path = path 289 | 290 | rep_data = self._rpc_send_and_read_answer(cmd_data, "storage_stat_request") 291 | 292 | if rep_data.command_status != 0: 293 | raise FlipperProtoException( 294 | f"{rep_data.command_status}: {self.Status_values_by_number[rep_data.command_status].name} path={path}" 295 | ) 296 | 297 | return MessageToDict( 298 | message=rep_data.storage_stat_response.file, 299 | including_default_value_fields=True, 300 | ) 301 | 302 | def rpc_md5sum(self, path=None) -> str: 303 | """get md5 of file 304 | 305 | Parameters 306 | ---------- 307 | path : str 308 | path to file on flipper device 309 | path must be full path 310 | path must not have trailing '/' 311 | 312 | Raises 313 | ---------- 314 | FlipperProtoException 315 | 316 | """ 317 | if self._debug: 318 | print(f"\ncmd_md5sum path={path}") 319 | 320 | cmd_data = storage_pb2.Md5sumRequest() 321 | cmd_data.path = path 322 | 323 | rep_data = self._rpc_send_and_read_answer(cmd_data, "storage_md5sum_request") 324 | 325 | if rep_data.command_status != 0: 326 | raise FlipperProtoException( 327 | f"{rep_data.command_status} : {self.Status_values_by_number[rep_data.command_status].name} path={path}" 328 | ) 329 | 330 | return rep_data.storage_md5sum_response.md5sum 331 | 332 | def _mkdir_path(self, path) -> None: 333 | if self._debug: 334 | print(f"\n_mkdir_path path={path}") 335 | cmd_data = storage_pb2.MkdirRequest() 336 | cmd_data.path = path 337 | rep_data = self._rpc_send_and_read_answer(cmd_data, "storage_mkdir_request") 338 | return rep_data.command_status 339 | 340 | def rpc_mkdir(self, path) -> None: 341 | """creates a new directory 342 | 343 | Parameters 344 | ---------- 345 | path : str 346 | path for new directory on flipper device 347 | path must be full path 348 | path must not have trailing '/' 349 | 350 | Raises 351 | ---------- 352 | FlipperProtoException 353 | 354 | """ 355 | 356 | if self._debug: 357 | print(f"\ncmd_mkdir path={path}") 358 | 359 | cmd_data = storage_pb2.MkdirRequest() 360 | cmd_data.path = path 361 | 362 | rep_data = self._rpc_send_and_read_answer(cmd_data, "storage_mkdir_request") 363 | 364 | if rep_data.command_status != 0: 365 | raise FlipperProtoException( 366 | f"{rep_data.command_status} : {self.Status_values_by_number[rep_data.command_status].name} path={path}" 367 | ) 368 | 369 | def _rpc_delete(self, path=None, recursive=False): 370 | cmd_data = storage_pb2.DeleteRequest() 371 | cmd_data.path = path 372 | cmd_data.recursive = recursive 373 | rep_data = self._rpc_send_and_read_answer(cmd_data, "storage_delete_request") 374 | 375 | return rep_data.command_status 376 | 377 | def rpc_delete(self, path=None, recursive=False) -> None: 378 | """delete file or dir 379 | 380 | Parameters 381 | ---------- 382 | path : str 383 | path to file or dir on flipper device 384 | path must be full path 385 | path must not have trailing '/' 386 | 387 | Raises 388 | ---------- 389 | FlipperProtoException 390 | 391 | """ 392 | 393 | if self._debug: 394 | print(f"\ncmd_delete path={path} recursive={recursive}") 395 | 396 | if path is None: 397 | raise ValueError("path can not be None") 398 | 399 | cmd_data = storage_pb2.DeleteRequest() 400 | 401 | cmd_data.path = path 402 | cmd_data.recursive = recursive 403 | 404 | rep_data = self._rpc_send_and_read_answer(cmd_data, "storage_delete_request") 405 | 406 | if rep_data.command_status != 0: 407 | raise FlipperProtoException( 408 | f"{rep_data.command_status} : {self.Status_values_by_number[rep_data.command_status].name} path={path}" 409 | ) 410 | 411 | def rpc_rename_file(self, old_path=None, new_path=None) -> None: 412 | """rename file or dir 413 | 414 | Parameters 415 | ---------- 416 | old_path : str 417 | path to file or dir on flipper device 418 | new_path : str 419 | path to file or dir on flipper device 420 | 421 | paths must be full path 422 | paths must not have trailing '/' 423 | 424 | Raises 425 | ---------- 426 | FlipperProtoException 427 | 428 | """ 429 | 430 | if self._debug: 431 | print(f"\ncmd_rename_file old_path={old_path} new_path={new_path}") 432 | 433 | cmd_data = storage_pb2.RenameRequest() 434 | cmd_data.old_path = old_path 435 | cmd_data.new_path = new_path 436 | # pprint.pprint(MessageToDict(message=cmd_data, including_default_value_fields=True)) 437 | 438 | rep_data = self._rpc_send_and_read_answer(cmd_data, "storage_rename_request") 439 | 440 | if rep_data.command_status != 0: 441 | raise FlipperProtoException( 442 | f"{rep_data.command_status} : {self.Status_values_by_number[rep_data.command_status].name} old_path={old_path} new_path={new_path}" 443 | ) 444 | 445 | # return # rep_data.command_status 446 | 447 | def rpc_storage_list(self, path="/ext") -> list: 448 | """get file & dir listing 449 | 450 | Parameters 451 | ---------- 452 | path : str 453 | path to filesystem 454 | path must be full path to and existng Folder/directory 455 | path must not have trailing '/' 456 | 457 | Returns: 458 | ---------- 459 | list 460 | 461 | Raises 462 | ---------- 463 | FlipperProtoException 464 | 465 | """ 466 | # print("f_code.co_name", sys._getframe().f_code.co_name) 467 | storage_response = [] 468 | cmd_data = storage_pb2.ListRequest() 469 | 470 | cmd_data.path = path 471 | rep_data = self._rpc_send_and_read_answer( 472 | cmd_data, "storage_list_request" 473 | ) # has_next=True) 474 | 475 | if self._debug > 3: 476 | for i in rep_data.storage_list_response.file: 477 | print(type(i)) 478 | # print(dir(i)) 479 | print(">>", i.name, i.type, i.size) 480 | print("+>", i.SerializeToString()) 481 | 482 | if rep_data.command_status != 0: 483 | raise FlipperProtoException( 484 | f"{rep_data.command_status} : {self.Status_values_by_number[rep_data.command_status].name} path={path}" 485 | ) 486 | 487 | storage_response.extend( 488 | MessageToDict( 489 | message=rep_data.storage_list_response, 490 | including_default_value_fields=True, 491 | )["file"] 492 | ) 493 | 494 | # print("rep_data.has_next:", rep_data.has_next) 495 | 496 | while rep_data.has_next: 497 | rep_data = self._rpc_read_answer() 498 | storage_response.extend( 499 | MessageToDict( 500 | message=rep_data.storage_list_response, 501 | including_default_value_fields=True, 502 | )["file"] 503 | ) 504 | 505 | # return sorted(storage_response, key = lambda x: (x['type'], x['name'].lower())) 506 | return storage_response 507 | 508 | def rpc_storage_untar(self, tar_path, out_path) -> None: 509 | """extract a tar archive 510 | 511 | Parameters 512 | ---------- 513 | tar_path : str 514 | path to the tar archive file 515 | must be a an absolute path to an existing file 516 | out_path : str 517 | output path to extract files into 518 | must be an absolute path to an existing directory 519 | with NO trailing slash (/) 520 | 521 | Returns: 522 | --------- 523 | None 524 | 525 | Raises: 526 | --------- 527 | FlipperProtoException 528 | """ 529 | 530 | cmd_data = storage_pb2.TarExtractRequest() 531 | cmd_data.tar_path = tar_path 532 | cmd_data.out_path = out_path 533 | 534 | rep_data = self._rpc_send_and_read_answer( 535 | cmd_data, 536 | "storage_tar_extract_request" 537 | ) 538 | 539 | if rep_data.command_status != 0: 540 | raise FlipperProtoException( 541 | self.Status_values_by_number[rep_data.command_status].name 542 | ) 543 | -------------------------------------------------------------------------------- /flipperzero_protobuf/flipper_sys.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | FlipperProto system related function Class 4 | """ 5 | 6 | # import sys 7 | # import os 8 | import datetime 9 | 10 | from google.protobuf.json_format import MessageToDict 11 | 12 | from .flipper_base import FlipperProtoException, InputTypeException 13 | from .flipperzero_protobuf_compiled import flipper_pb2, system_pb2 14 | 15 | # import pprint 16 | 17 | # from nis import match 18 | # from numpy import mat 19 | 20 | # pylint: disable=line-too-long, no-member 21 | 22 | 23 | __all__ = ["FlipperProtoSys"] 24 | 25 | 26 | class FlipperProtoSys: 27 | """FlipperProto sys function Class""" 28 | 29 | # FactoryReset 30 | def rpc_factory_reset(self) -> None: 31 | """Factory Reset 32 | 33 | Parameters 34 | ---------- 35 | None 36 | 37 | Returns 38 | ---------- 39 | None 40 | 41 | Raises 42 | ---------- 43 | FlipperProtoException 44 | 45 | """ 46 | 47 | cmd_data = system_pb2.FactoryResetRequest() 48 | rep_data = self._rpc_send_and_read_answer( 49 | cmd_data, "system_factory_reset_request" 50 | ) 51 | 52 | if rep_data.command_status != 0: 53 | raise FlipperProtoException( 54 | self.Status_values_by_number[rep_data.command_status].name 55 | ) 56 | 57 | # Update 58 | def rpc_update(self, update_manifest="") -> None: 59 | """Update 60 | 61 | Parameters 62 | ---------- 63 | update_manifest : str 64 | 65 | Returns 66 | ---------- 67 | None 68 | 69 | code ; str 70 | 0 OK 71 | 1 ManifestPathInvalid 72 | 2 ManifestFolderNotFound 73 | 3 ManifestInvalid 74 | 4 StageMissing 75 | 5 StageIntegrityError 76 | 6 ManifestPointerError 77 | 7 TargetMismatch 78 | 8 OutdatedManifestVersion 79 | 9 IntFull 80 | 81 | """ 82 | cmd_data = system_pb2.UpdateRequest() 83 | cmd_data.update_manifest = update_manifest 84 | 85 | rep_data = self._rpc_send_and_read_answer(cmd_data, "system_update_request") 86 | 87 | if rep_data.command_status != 0: 88 | raise FlipperProtoException( 89 | f"{self.Status_values_by_number[rep_data.command_status].name} update_manifest={update_manifest}" 90 | ) 91 | 92 | # Reboot 93 | def rpc_reboot(self, mode=0) -> None: 94 | """Reboot flipper 95 | 96 | Parameters 97 | ---------- 98 | mode : int or str 99 | 0 = OS 100 | 1 = DFU 101 | 2 = UPDATE 102 | 103 | Returns 104 | ---------- 105 | None 106 | 107 | Raises 108 | ---------- 109 | InputTypeException 110 | FlipperProtoException 111 | 112 | """ 113 | # pylint: disable=broad-except 114 | cmd_data = system_pb2.RebootRequest() 115 | 116 | if mode not in ["OS", "DFU", "UPDATE"]: 117 | raise InputTypeException("Invalid Reboot mode") 118 | cmd_data.mode = getattr(cmd_data, mode) 119 | 120 | try: 121 | # nothing happens if only cmd_send is called and response is not read 122 | # rep_data = self._rpc_send(cmd_data, 'system_reboot_request') 123 | 124 | # gets SerialException from attempt to read response 125 | rep_data = self._rpc_send_and_read_answer(cmd_data, "system_reboot_request") 126 | # except serial.serialutil.SerialException as _e: 127 | except Exception as _e: 128 | return 129 | 130 | # we should not get here 131 | if rep_data.command_status != 0: 132 | raise FlipperProtoException( 133 | f"{self.Status_values_by_number[rep_data.command_status].name} mode={mode}" 134 | ) 135 | 136 | # PowerInfo 137 | def rpc_power_info(self) -> list[tuple[str, str]]: 138 | """Power info / charging status 139 | 140 | Parameters 141 | ---------- 142 | None 143 | 144 | Returns 145 | ---------- 146 | list[tuple[key, value : str]] 147 | 148 | Raises 149 | ---------- 150 | FlipperProtoException 151 | 152 | """ 153 | cmd_data = system_pb2.PowerInfoRequest() 154 | rep_data = self._rpc_send_and_read_answer(cmd_data, "system_power_info_request") 155 | 156 | if rep_data.command_status != 0: 157 | raise FlipperProtoException( 158 | self.Status_values_by_number[rep_data.command_status].name 159 | ) 160 | 161 | ret = [] 162 | 163 | while True: 164 | ret.append( 165 | ( 166 | rep_data.system_power_info_response.key, 167 | rep_data.system_power_info_response.value, 168 | ) 169 | ) 170 | 171 | if rep_data.has_next: 172 | rep_data = self._rpc_read_answer() 173 | else: 174 | break 175 | 176 | return ret 177 | 178 | # DeviceInfo 179 | def rpc_device_info(self) -> list[tuple[str, str]]: 180 | """Device Info 181 | 182 | Return 183 | ---------- 184 | list[tuple[key, value : str]] 185 | 186 | Raises 187 | ---------- 188 | FlipperProtoException 189 | 190 | """ 191 | 192 | cmd_data = system_pb2.DeviceInfoRequest() 193 | 194 | rep_data = self._rpc_send_and_read_answer( 195 | cmd_data, "system_device_info_request" 196 | ) 197 | 198 | if rep_data.command_status != 0: 199 | raise FlipperProtoException( 200 | self.Status_values_by_number[rep_data.command_status].name 201 | ) 202 | 203 | ret = [] 204 | 205 | while True: 206 | ret.append( 207 | ( 208 | rep_data.system_device_info_response.key, 209 | rep_data.system_device_info_response.value, 210 | ) 211 | ) 212 | 213 | if rep_data.has_next: 214 | rep_data = self._rpc_read_answer() 215 | else: 216 | break 217 | 218 | return ret 219 | 220 | # ProtobufVersion 221 | def rpc_protobuf_version(self) -> tuple[int, int]: 222 | """Protobuf Version 223 | 224 | Parameters 225 | ---------- 226 | None 227 | 228 | Return 229 | ---------- 230 | major, minor : int 231 | 232 | Raises 233 | ---------- 234 | FlipperProtoException 235 | 236 | """ 237 | cmd_data = system_pb2.ProtobufVersionRequest() 238 | rep_data = self._rpc_send_and_read_answer( 239 | cmd_data, "system_protobuf_version_request" 240 | ) 241 | 242 | if rep_data.command_status != 0: 243 | raise FlipperProtoException( 244 | self.Status_values_by_number[rep_data.command_status].name 245 | ) 246 | 247 | return ( 248 | rep_data.system_protobuf_version_response.major, 249 | rep_data.system_protobuf_version_response.minor, 250 | ) 251 | 252 | # GetDateTime 253 | def rpc_get_datetime(self) -> dict: 254 | """Get system Date and Time 255 | 256 | Parameters 257 | ---------- 258 | None 259 | 260 | Returns 261 | ---------- 262 | dict 263 | keys: 'year', 'month', 'day', 'hour', 'minute', 'second', 'weekday' 264 | 265 | Raises 266 | ---------- 267 | FlipperProtoException 268 | 269 | """ 270 | cmd_data = system_pb2.GetDateTimeRequest() 271 | rep_data = self._rpc_send_and_read_answer( 272 | cmd_data, "system_get_datetime_request" 273 | ) 274 | if rep_data.command_status != 0: 275 | raise FlipperProtoException( 276 | self.Status_values_by_number[rep_data.command_status].name 277 | ) 278 | return MessageToDict(rep_data.system_get_datetime_response)["datetime"] 279 | 280 | # SetDateTime 281 | def rpc_set_datetime(self, arg_datetm=None) -> None: 282 | """Set system Date and Time 283 | 284 | Parameters 285 | ---------- 286 | datetm : dict or datetime obj 287 | dict keys: 'year', 'month', 'day', 'hour', 'minute', 'second', 'weekday' 288 | datetime obj 289 | None (default) method datetime.datetime.now() is called 290 | 291 | Returns 292 | ---------- 293 | None 294 | 295 | Raises 296 | ---------- 297 | InputTypeException 298 | FlipperProtoException 299 | 300 | """ 301 | if arg_datetm is None: 302 | datetm = datetime.datetime.now() 303 | else: 304 | datetm = arg_datetm 305 | 306 | cmd_data = system_pb2.SetDateTimeRequest() 307 | 308 | if isinstance(datetm, datetime.datetime): 309 | cmd_data.datetime.year = datetm.year 310 | cmd_data.datetime.month = datetm.month 311 | cmd_data.datetime.day = datetm.day 312 | cmd_data.datetime.hour = datetm.hour 313 | cmd_data.datetime.minute = datetm.minute 314 | cmd_data.datetime.second = datetm.second 315 | cmd_data.datetime.weekday = datetm.isoweekday() 316 | elif isinstance(datetm, dict): 317 | cmd_data.datetime.update(datetm) 318 | else: 319 | raise InputTypeException("Invalid datetime value") 320 | 321 | rep_data = self._rpc_send_and_read_answer( 322 | cmd_data, "system_set_datetime_request" 323 | ) 324 | 325 | if rep_data.command_status != 0: 326 | raise FlipperProtoException( 327 | f"{self.Status_values_by_number[rep_data.command_status].name} arg_datetm={arg_datetm}" 328 | ) 329 | 330 | # Ping 331 | def rpc_system_ping(self, data=bytes([0xDE, 0xAD, 0xBE, 0xEF])) -> list: 332 | """Ping flipper 333 | 334 | Parameters 335 | ---------- 336 | data : bytes 337 | 338 | Returns 339 | ---------- 340 | list 341 | 342 | Raises 343 | ---------- 344 | InputTypeException 345 | FlipperProtoException 346 | 347 | """ 348 | 349 | cmd_data = system_pb2.PingRequest() 350 | 351 | if not isinstance(data, bytes): 352 | raise InputTypeException("Invalid Ping data value") 353 | 354 | cmd_data.data = data 355 | rep_data = self._rpc_send_and_read_answer(cmd_data, "system_ping_request") 356 | 357 | if rep_data.command_status != 0: 358 | raise FlipperProtoException( 359 | f"{self.Status_values_by_number[rep_data.command_status].name} data={data}" 360 | ) 361 | 362 | return rep_data.system_ping_response.data 363 | 364 | # PlayAudiovisualAlert 365 | def rpc_audiovisual_alert(self) -> None: 366 | """Launch audiovisual alert on flipper ?? 367 | 368 | Parameters 369 | ---------- 370 | None 371 | 372 | Returns 373 | ---------- 374 | None 375 | 376 | Raises 377 | ---------- 378 | FlipperProtoException 379 | 380 | """ 381 | 382 | cmd_data = system_pb2.PlayAudiovisualAlertRequest() 383 | rep_data = self._rpc_send_and_read_answer( 384 | cmd_data, "system_play_audiovisual_alert_request" 385 | ) 386 | 387 | if rep_data.command_status != 0: 388 | raise FlipperProtoException( 389 | self.Status_values_by_number[rep_data.command_status].name 390 | ) 391 | 392 | # pylint: disable=protected-access 393 | def rpc_stop_session(self) -> None: 394 | """Stop RPC session 395 | 396 | Parameters 397 | ---------- 398 | None 399 | 400 | Returns 401 | ---------- 402 | None 403 | 404 | Raises 405 | ---------- 406 | FlipperProtoException 407 | 408 | """ 409 | 410 | cmd_data = flipper_pb2.StopSession() 411 | rep_data = self._rpc_send_and_read_answer(cmd_data, "stop_session") 412 | 413 | if rep_data.command_status != 0: 414 | raise FlipperProtoException( 415 | self.Status_values_by_number[rep_data.command_status].name 416 | ) 417 | 418 | self.flip._in_session = False 419 | -------------------------------------------------------------------------------- /flipperzero_protobuf/flipperzero_protobuf_compiled/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flipperdevices/flipperzero_protobuf_py/412d5183064801e420b9d01443bfc9211a8294a5/flipperzero_protobuf/flipperzero_protobuf_compiled/__init__.py -------------------------------------------------------------------------------- /flipperzero_protobuf/flipperzero_protobuf_compiled/__init__.pyi: -------------------------------------------------------------------------------- 1 | from . import __pycache__ 2 | -------------------------------------------------------------------------------- /flipperzero_protobuf/flipperzero_protobuf_compiled/application_pb2.py: -------------------------------------------------------------------------------- 1 | """Generated protocol buffer code.""" 2 | from google.protobuf import descriptor as _descriptor 3 | from google.protobuf import descriptor_pool as _descriptor_pool 4 | from google.protobuf import symbol_database as _symbol_database 5 | from google.protobuf.internal import builder as _builder 6 | _sym_db = _symbol_database.Default() 7 | DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x11application.proto\x12\x06PB_App"*\n\x0cStartRequest\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x0c\n\x04args\x18\x02 \x01(\t"\x13\n\x11LockStatusRequest"$\n\x12LockStatusResponse\x12\x0e\n\x06locked\x18\x01 \x01(\x08"\x10\n\x0eAppExitRequest""\n\x12AppLoadFileRequest\x12\x0c\n\x04path\x18\x01 \x01(\t"4\n\x15AppButtonPressRequest\x12\x0c\n\x04args\x18\x01 \x01(\t\x12\r\n\x05index\x18\x02 \x01(\x05"\x19\n\x17AppButtonReleaseRequest"3\n\x10AppStateResponse\x12\x1f\n\x05state\x18\x01 \x01(\x0e2\x10.PB_App.AppState"\x11\n\x0fGetErrorRequest".\n\x10GetErrorResponse\x12\x0c\n\x04code\x18\x01 \x01(\r\x12\x0c\n\x04text\x18\x02 \x01(\t"#\n\x13DataExchangeRequest\x12\x0c\n\x04data\x18\x01 \x01(\x0c*+\n\x08AppState\x12\x0e\n\nAPP_CLOSED\x10\x00\x12\x0f\n\x0bAPP_STARTED\x10\x01B!\n\x1fcom.flipperdevices.protobuf.appb\x06proto3') 8 | _globals = globals() 9 | _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) 10 | _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'application_pb2', _globals) 11 | if _descriptor._USE_C_DESCRIPTORS == False: 12 | _globals['DESCRIPTOR']._options = None 13 | _globals['DESCRIPTOR']._serialized_options = b'\n\x1fcom.flipperdevices.protobuf.app' 14 | _globals['_APPSTATE']._serialized_start = 424 15 | _globals['_APPSTATE']._serialized_end = 467 16 | _globals['_STARTREQUEST']._serialized_start = 29 17 | _globals['_STARTREQUEST']._serialized_end = 71 18 | _globals['_LOCKSTATUSREQUEST']._serialized_start = 73 19 | _globals['_LOCKSTATUSREQUEST']._serialized_end = 92 20 | _globals['_LOCKSTATUSRESPONSE']._serialized_start = 94 21 | _globals['_LOCKSTATUSRESPONSE']._serialized_end = 130 22 | _globals['_APPEXITREQUEST']._serialized_start = 132 23 | _globals['_APPEXITREQUEST']._serialized_end = 148 24 | _globals['_APPLOADFILEREQUEST']._serialized_start = 150 25 | _globals['_APPLOADFILEREQUEST']._serialized_end = 184 26 | _globals['_APPBUTTONPRESSREQUEST']._serialized_start = 186 27 | _globals['_APPBUTTONPRESSREQUEST']._serialized_end = 238 28 | _globals['_APPBUTTONRELEASEREQUEST']._serialized_start = 240 29 | _globals['_APPBUTTONRELEASEREQUEST']._serialized_end = 265 30 | _globals['_APPSTATERESPONSE']._serialized_start = 267 31 | _globals['_APPSTATERESPONSE']._serialized_end = 318 32 | _globals['_GETERRORREQUEST']._serialized_start = 320 33 | _globals['_GETERRORREQUEST']._serialized_end = 337 34 | _globals['_GETERRORRESPONSE']._serialized_start = 339 35 | _globals['_GETERRORRESPONSE']._serialized_end = 385 36 | _globals['_DATAEXCHANGEREQUEST']._serialized_start = 387 37 | _globals['_DATAEXCHANGEREQUEST']._serialized_end = 422 -------------------------------------------------------------------------------- /flipperzero_protobuf/flipperzero_protobuf_compiled/desktop_pb2.py: -------------------------------------------------------------------------------- 1 | """Generated protocol buffer code.""" 2 | from google.protobuf import descriptor as _descriptor 3 | from google.protobuf import descriptor_pool as _descriptor_pool 4 | from google.protobuf import symbol_database as _symbol_database 5 | from google.protobuf.internal import builder as _builder 6 | _sym_db = _symbol_database.Default() 7 | DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\rdesktop.proto\x12\nPB_Desktop"\x11\n\x0fIsLockedRequest"\x0f\n\rUnlockRequest"\x18\n\x16StatusSubscribeRequest"\x1a\n\x18StatusUnsubscribeRequest"\x18\n\x06Status\x12\x0e\n\x06locked\x18\x01 \x01(\x08B%\n#com.flipperdevices.protobuf.desktopb\x06proto3') 8 | _globals = globals() 9 | _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) 10 | _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'desktop_pb2', _globals) 11 | if _descriptor._USE_C_DESCRIPTORS == False: 12 | _globals['DESCRIPTOR']._options = None 13 | _globals['DESCRIPTOR']._serialized_options = b'\n#com.flipperdevices.protobuf.desktop' 14 | _globals['_ISLOCKEDREQUEST']._serialized_start = 29 15 | _globals['_ISLOCKEDREQUEST']._serialized_end = 46 16 | _globals['_UNLOCKREQUEST']._serialized_start = 48 17 | _globals['_UNLOCKREQUEST']._serialized_end = 63 18 | _globals['_STATUSSUBSCRIBEREQUEST']._serialized_start = 65 19 | _globals['_STATUSSUBSCRIBEREQUEST']._serialized_end = 89 20 | _globals['_STATUSUNSUBSCRIBEREQUEST']._serialized_start = 91 21 | _globals['_STATUSUNSUBSCRIBEREQUEST']._serialized_end = 117 22 | _globals['_STATUS']._serialized_start = 119 23 | _globals['_STATUS']._serialized_end = 143 -------------------------------------------------------------------------------- /flipperzero_protobuf/flipperzero_protobuf_compiled/flipper_pb2.py: -------------------------------------------------------------------------------- 1 | """Generated protocol buffer code.""" 2 | from google.protobuf import descriptor as _descriptor 3 | from google.protobuf import descriptor_pool as _descriptor_pool 4 | from google.protobuf import symbol_database as _symbol_database 5 | from google.protobuf.internal import builder as _builder 6 | _sym_db = _symbol_database.Default() 7 | from . import storage_pb2 as storage__pb2 8 | from . import system_pb2 as system__pb2 9 | from . import application_pb2 as application__pb2 10 | from . import gui_pb2 as gui__pb2 11 | from . import gpio_pb2 as gpio__pb2 12 | from . import property_pb2 as property__pb2 13 | from . import desktop_pb2 as desktop__pb2 14 | DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\rflipper.proto\x12\x02PB\x1a\rstorage.proto\x1a\x0csystem.proto\x1a\x11application.proto\x1a\tgui.proto\x1a\ngpio.proto\x1a\x0eproperty.proto\x1a\rdesktop.proto"\x07\n\x05Empty"\r\n\x0bStopSession"\x8f"\n\x04Main\x12\x12\n\ncommand_id\x18\x01 \x01(\r\x12)\n\x0ecommand_status\x18\x02 \x01(\x0e2\x11.PB.CommandStatus\x12\x10\n\x08has_next\x18\x03 \x01(\x08\x12\x1a\n\x05empty\x18\x04 \x01(\x0b2\t.PB.EmptyH\x00\x12\'\n\x0cstop_session\x18\x13 \x01(\x0b2\x0f.PB.StopSessionH\x00\x125\n\x13system_ping_request\x18\x05 \x01(\x0b2\x16.PB_System.PingRequestH\x00\x127\n\x14system_ping_response\x18\x06 \x01(\x0b2\x17.PB_System.PingResponseH\x00\x129\n\x15system_reboot_request\x18\x1f \x01(\x0b2\x18.PB_System.RebootRequestH\x00\x12B\n\x1asystem_device_info_request\x18 \x01(\x0b2\x1c.PB_System.DeviceInfoRequestH\x00\x12D\n\x1bsystem_device_info_response\x18! \x01(\x0b2\x1d.PB_System.DeviceInfoResponseH\x00\x12F\n\x1csystem_factory_reset_request\x18" \x01(\x0b2\x1e.PB_System.FactoryResetRequestH\x00\x12D\n\x1bsystem_get_datetime_request\x18# \x01(\x0b2\x1d.PB_System.GetDateTimeRequestH\x00\x12F\n\x1csystem_get_datetime_response\x18$ \x01(\x0b2\x1e.PB_System.GetDateTimeResponseH\x00\x12D\n\x1bsystem_set_datetime_request\x18% \x01(\x0b2\x1d.PB_System.SetDateTimeRequestH\x00\x12W\n%system_play_audiovisual_alert_request\x18& \x01(\x0b2&.PB_System.PlayAudiovisualAlertRequestH\x00\x12L\n\x1fsystem_protobuf_version_request\x18\' \x01(\x0b2!.PB_System.ProtobufVersionRequestH\x00\x12N\n system_protobuf_version_response\x18( \x01(\x0b2".PB_System.ProtobufVersionResponseH\x00\x129\n\x15system_update_request\x18) \x01(\x0b2\x18.PB_System.UpdateRequestH\x00\x12;\n\x16system_update_response\x18. \x01(\x0b2\x19.PB_System.UpdateResponseH\x00\x12@\n\x19system_power_info_request\x18, \x01(\x0b2\x1b.PB_System.PowerInfoRequestH\x00\x12B\n\x1asystem_power_info_response\x18- \x01(\x0b2\x1c.PB_System.PowerInfoResponseH\x00\x127\n\x14storage_info_request\x18\x1c \x01(\x0b2\x17.PB_Storage.InfoRequestH\x00\x129\n\x15storage_info_response\x18\x1d \x01(\x0b2\x18.PB_Storage.InfoResponseH\x00\x12A\n\x19storage_timestamp_request\x18; \x01(\x0b2\x1c.PB_Storage.TimestampRequestH\x00\x12C\n\x1astorage_timestamp_response\x18< \x01(\x0b2\x1d.PB_Storage.TimestampResponseH\x00\x127\n\x14storage_stat_request\x18\x18 \x01(\x0b2\x17.PB_Storage.StatRequestH\x00\x129\n\x15storage_stat_response\x18\x19 \x01(\x0b2\x18.PB_Storage.StatResponseH\x00\x127\n\x14storage_list_request\x18\x07 \x01(\x0b2\x17.PB_Storage.ListRequestH\x00\x129\n\x15storage_list_response\x18\x08 \x01(\x0b2\x18.PB_Storage.ListResponseH\x00\x127\n\x14storage_read_request\x18\t \x01(\x0b2\x17.PB_Storage.ReadRequestH\x00\x129\n\x15storage_read_response\x18\n \x01(\x0b2\x18.PB_Storage.ReadResponseH\x00\x129\n\x15storage_write_request\x18\x0b \x01(\x0b2\x18.PB_Storage.WriteRequestH\x00\x12;\n\x16storage_delete_request\x18\x0c \x01(\x0b2\x19.PB_Storage.DeleteRequestH\x00\x129\n\x15storage_mkdir_request\x18\r \x01(\x0b2\x18.PB_Storage.MkdirRequestH\x00\x12;\n\x16storage_md5sum_request\x18\x0e \x01(\x0b2\x19.PB_Storage.Md5sumRequestH\x00\x12=\n\x17storage_md5sum_response\x18\x0f \x01(\x0b2\x1a.PB_Storage.Md5sumResponseH\x00\x12;\n\x16storage_rename_request\x18\x1e \x01(\x0b2\x19.PB_Storage.RenameRequestH\x00\x12H\n\x1dstorage_backup_create_request\x18* \x01(\x0b2\x1f.PB_Storage.BackupCreateRequestH\x00\x12J\n\x1estorage_backup_restore_request\x18+ \x01(\x0b2 .PB_Storage.BackupRestoreRequestH\x00\x12D\n\x1bstorage_tar_extract_request\x18G \x01(\x0b2\x1d.PB_Storage.TarExtractRequestH\x00\x121\n\x11app_start_request\x18\x10 \x01(\x0b2\x14.PB_App.StartRequestH\x00\x12<\n\x17app_lock_status_request\x18\x11 \x01(\x0b2\x19.PB_App.LockStatusRequestH\x00\x12>\n\x18app_lock_status_response\x18\x12 \x01(\x0b2\x1a.PB_App.LockStatusResponseH\x00\x122\n\x10app_exit_request\x18/ \x01(\x0b2\x16.PB_App.AppExitRequestH\x00\x12;\n\x15app_load_file_request\x180 \x01(\x0b2\x1a.PB_App.AppLoadFileRequestH\x00\x12A\n\x18app_button_press_request\x181 \x01(\x0b2\x1d.PB_App.AppButtonPressRequestH\x00\x12E\n\x1aapp_button_release_request\x182 \x01(\x0b2\x1f.PB_App.AppButtonReleaseRequestH\x00\x128\n\x15app_get_error_request\x18? \x01(\x0b2\x17.PB_App.GetErrorRequestH\x00\x12:\n\x16app_get_error_response\x18@ \x01(\x0b2\x18.PB_App.GetErrorResponseH\x00\x12@\n\x19app_data_exchange_request\x18A \x01(\x0b2\x1b.PB_App.DataExchangeRequestH\x00\x12K\n\x1fgui_start_screen_stream_request\x18\x14 \x01(\x0b2 .PB_Gui.StartScreenStreamRequestH\x00\x12I\n\x1egui_stop_screen_stream_request\x18\x15 \x01(\x0b2\x1f.PB_Gui.StopScreenStreamRequestH\x00\x12/\n\x10gui_screen_frame\x18\x16 \x01(\x0b2\x13.PB_Gui.ScreenFrameH\x00\x12E\n\x1cgui_send_input_event_request\x18\x17 \x01(\x0b2\x1d.PB_Gui.SendInputEventRequestH\x00\x12O\n!gui_start_virtual_display_request\x18\x1a \x01(\x0b2".PB_Gui.StartVirtualDisplayRequestH\x00\x12M\n gui_stop_virtual_display_request\x18\x1b \x01(\x0b2!.PB_Gui.StopVirtualDisplayRequestH\x00\x120\n\x11gpio_set_pin_mode\x183 \x01(\x0b2\x13.PB_Gpio.SetPinModeH\x00\x124\n\x13gpio_set_input_pull\x184 \x01(\x0b2\x15.PB_Gpio.SetInputPullH\x00\x120\n\x11gpio_get_pin_mode\x185 \x01(\x0b2\x13.PB_Gpio.GetPinModeH\x00\x12A\n\x1agpio_get_pin_mode_response\x186 \x01(\x0b2\x1b.PB_Gpio.GetPinModeResponseH\x00\x12)\n\rgpio_read_pin\x187 \x01(\x0b2\x10.PB_Gpio.ReadPinH\x00\x12:\n\x16gpio_read_pin_response\x188 \x01(\x0b2\x18.PB_Gpio.ReadPinResponseH\x00\x12+\n\x0egpio_write_pin\x189 \x01(\x0b2\x11.PB_Gpio.WritePinH\x00\x126\n\x12app_state_response\x18: \x01(\x0b2\x18.PB_App.AppStateResponseH\x00\x127\n\x14property_get_request\x18= \x01(\x0b2\x17.PB_Property.GetRequestH\x00\x129\n\x15property_get_response\x18> \x01(\x0b2\x18.PB_Property.GetResponseH\x00\x12@\n\x19desktop_is_locked_request\x18B \x01(\x0b2\x1b.PB_Desktop.IsLockedRequestH\x00\x12;\n\x16desktop_unlock_request\x18C \x01(\x0b2\x19.PB_Desktop.UnlockRequestH\x00\x12N\n desktop_status_subscribe_request\x18D \x01(\x0b2".PB_Desktop.StatusSubscribeRequestH\x00\x12R\n"desktop_status_unsubscribe_request\x18E \x01(\x0b2$.PB_Desktop.StatusUnsubscribeRequestH\x00\x12,\n\x0edesktop_status\x18F \x01(\x0b2\x12.PB_Desktop.StatusH\x00B\t\n\x07content"\x8b\x01\n\x06Region\x12\x14\n\x0ccountry_code\x18\x01 \x01(\x0c\x12\x1e\n\x05bands\x18\x02 \x03(\x0b2\x0f.PB.Region.Band\x1aK\n\x04Band\x12\r\n\x05start\x18\x01 \x01(\r\x12\x0b\n\x03end\x18\x02 \x01(\r\x12\x13\n\x0bpower_limit\x18\x03 \x01(\x05\x12\x12\n\nduty_cycle\x18\x04 \x01(\r*\xd6\x05\n\rCommandStatus\x12\x06\n\x02OK\x10\x00\x12\t\n\x05ERROR\x10\x01\x12\x10\n\x0cERROR_DECODE\x10\x02\x12\x19\n\x15ERROR_NOT_IMPLEMENTED\x10\x03\x12\x0e\n\nERROR_BUSY\x10\x04\x12(\n$ERROR_CONTINUOUS_COMMAND_INTERRUPTED\x10\x0e\x12\x1c\n\x18ERROR_INVALID_PARAMETERS\x10\x0f\x12\x1b\n\x17ERROR_STORAGE_NOT_READY\x10\x05\x12\x17\n\x13ERROR_STORAGE_EXIST\x10\x06\x12\x1b\n\x17ERROR_STORAGE_NOT_EXIST\x10\x07\x12#\n\x1fERROR_STORAGE_INVALID_PARAMETER\x10\x08\x12\x18\n\x14ERROR_STORAGE_DENIED\x10\t\x12\x1e\n\x1aERROR_STORAGE_INVALID_NAME\x10\n\x12\x1a\n\x16ERROR_STORAGE_INTERNAL\x10\x0b\x12!\n\x1dERROR_STORAGE_NOT_IMPLEMENTED\x10\x0c\x12\x1e\n\x1aERROR_STORAGE_ALREADY_OPEN\x10\r\x12\x1f\n\x1bERROR_STORAGE_DIR_NOT_EMPTY\x10\x12\x12\x18\n\x14ERROR_APP_CANT_START\x10\x10\x12\x1b\n\x17ERROR_APP_SYSTEM_LOCKED\x10\x11\x12\x19\n\x15ERROR_APP_NOT_RUNNING\x10\x15\x12\x17\n\x13ERROR_APP_CMD_ERROR\x10\x16\x12)\n%ERROR_VIRTUAL_DISPLAY_ALREADY_STARTED\x10\x13\x12%\n!ERROR_VIRTUAL_DISPLAY_NOT_STARTED\x10\x14\x12\x1d\n\x19ERROR_GPIO_MODE_INCORRECT\x10:\x12\x1f\n\x1bERROR_GPIO_UNKNOWN_PIN_MODE\x10;B\x1d\n\x1bcom.flipperdevices.protobufb\x06proto3') 15 | _globals = globals() 16 | _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) 17 | _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'flipper_pb2', _globals) 18 | if _descriptor._USE_C_DESCRIPTORS == False: 19 | _globals['DESCRIPTOR']._options = None 20 | _globals['DESCRIPTOR']._serialized_options = b'\n\x1bcom.flipperdevices.protobuf' 21 | _globals['_COMMANDSTATUS']._serialized_start = 4660 22 | _globals['_COMMANDSTATUS']._serialized_end = 5386 23 | _globals['_EMPTY']._serialized_start = 123 24 | _globals['_EMPTY']._serialized_end = 130 25 | _globals['_STOPSESSION']._serialized_start = 132 26 | _globals['_STOPSESSION']._serialized_end = 145 27 | _globals['_MAIN']._serialized_start = 148 28 | _globals['_MAIN']._serialized_end = 4515 29 | _globals['_REGION']._serialized_start = 4518 30 | _globals['_REGION']._serialized_end = 4657 31 | _globals['_REGION_BAND']._serialized_start = 4582 32 | _globals['_REGION_BAND']._serialized_end = 4657 -------------------------------------------------------------------------------- /flipperzero_protobuf/flipperzero_protobuf_compiled/gpio_pb2.py: -------------------------------------------------------------------------------- 1 | """Generated protocol buffer code.""" 2 | from google.protobuf import descriptor as _descriptor 3 | from google.protobuf import descriptor_pool as _descriptor_pool 4 | from google.protobuf import symbol_database as _symbol_database 5 | from google.protobuf.internal import builder as _builder 6 | _sym_db = _symbol_database.Default() 7 | DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\ngpio.proto\x12\x07PB_Gpio"O\n\nSetPinMode\x12\x1d\n\x03pin\x18\x01 \x01(\x0e2\x10.PB_Gpio.GpioPin\x12"\n\x04mode\x18\x02 \x01(\x0e2\x14.PB_Gpio.GpioPinMode"X\n\x0cSetInputPull\x12\x1d\n\x03pin\x18\x01 \x01(\x0e2\x10.PB_Gpio.GpioPin\x12)\n\tpull_mode\x18\x02 \x01(\x0e2\x16.PB_Gpio.GpioInputPull"+\n\nGetPinMode\x12\x1d\n\x03pin\x18\x01 \x01(\x0e2\x10.PB_Gpio.GpioPin"8\n\x12GetPinModeResponse\x12"\n\x04mode\x18\x01 \x01(\x0e2\x14.PB_Gpio.GpioPinMode"(\n\x07ReadPin\x12\x1d\n\x03pin\x18\x01 \x01(\x0e2\x10.PB_Gpio.GpioPin" \n\x0fReadPinResponse\x12\r\n\x05value\x18\x02 \x01(\r"8\n\x08WritePin\x12\x1d\n\x03pin\x18\x01 \x01(\x0e2\x10.PB_Gpio.GpioPin\x12\r\n\x05value\x18\x02 \x01(\r*Q\n\x07GpioPin\x12\x07\n\x03PC0\x10\x00\x12\x07\n\x03PC1\x10\x01\x12\x07\n\x03PC3\x10\x02\x12\x07\n\x03PB2\x10\x03\x12\x07\n\x03PB3\x10\x04\x12\x07\n\x03PA4\x10\x05\x12\x07\n\x03PA6\x10\x06\x12\x07\n\x03PA7\x10\x07*$\n\x0bGpioPinMode\x12\n\n\x06OUTPUT\x10\x00\x12\t\n\x05INPUT\x10\x01*)\n\rGpioInputPull\x12\x06\n\x02NO\x10\x00\x12\x06\n\x02UP\x10\x01\x12\x08\n\x04DOWN\x10\x02B"\n com.flipperdevices.protobuf.gpiob\x06proto3') 8 | _globals = globals() 9 | _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) 10 | _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'gpio_pb2', _globals) 11 | if _descriptor._USE_C_DESCRIPTORS == False: 12 | _globals['DESCRIPTOR']._options = None 13 | _globals['DESCRIPTOR']._serialized_options = b'\n com.flipperdevices.protobuf.gpio' 14 | _globals['_GPIOPIN']._serialized_start = 431 15 | _globals['_GPIOPIN']._serialized_end = 512 16 | _globals['_GPIOPINMODE']._serialized_start = 514 17 | _globals['_GPIOPINMODE']._serialized_end = 550 18 | _globals['_GPIOINPUTPULL']._serialized_start = 552 19 | _globals['_GPIOINPUTPULL']._serialized_end = 593 20 | _globals['_SETPINMODE']._serialized_start = 23 21 | _globals['_SETPINMODE']._serialized_end = 102 22 | _globals['_SETINPUTPULL']._serialized_start = 104 23 | _globals['_SETINPUTPULL']._serialized_end = 192 24 | _globals['_GETPINMODE']._serialized_start = 194 25 | _globals['_GETPINMODE']._serialized_end = 237 26 | _globals['_GETPINMODERESPONSE']._serialized_start = 239 27 | _globals['_GETPINMODERESPONSE']._serialized_end = 295 28 | _globals['_READPIN']._serialized_start = 297 29 | _globals['_READPIN']._serialized_end = 337 30 | _globals['_READPINRESPONSE']._serialized_start = 339 31 | _globals['_READPINRESPONSE']._serialized_end = 371 32 | _globals['_WRITEPIN']._serialized_start = 373 33 | _globals['_WRITEPIN']._serialized_end = 429 -------------------------------------------------------------------------------- /flipperzero_protobuf/flipperzero_protobuf_compiled/gui_pb2.py: -------------------------------------------------------------------------------- 1 | """Generated protocol buffer code.""" 2 | from google.protobuf import descriptor as _descriptor 3 | from google.protobuf import descriptor_pool as _descriptor_pool 4 | from google.protobuf import symbol_database as _symbol_database 5 | from google.protobuf.internal import builder as _builder 6 | _sym_db = _symbol_database.Default() 7 | DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\tgui.proto\x12\x06PB_Gui"K\n\x0bScreenFrame\x12\x0c\n\x04data\x18\x01 \x01(\x0c\x12.\n\x0borientation\x18\x02 \x01(\x0e2\x19.PB_Gui.ScreenOrientation"\x1a\n\x18StartScreenStreamRequest"\x19\n\x17StopScreenStreamRequest"W\n\x15SendInputEventRequest\x12\x1d\n\x03key\x18\x01 \x01(\x0e2\x10.PB_Gui.InputKey\x12\x1f\n\x04type\x18\x02 \x01(\x0e2\x11.PB_Gui.InputType"Z\n\x1aStartVirtualDisplayRequest\x12(\n\x0bfirst_frame\x18\x01 \x01(\x0b2\x13.PB_Gui.ScreenFrame\x12\x12\n\nsend_input\x18\x02 \x01(\x08"\x1b\n\x19StopVirtualDisplayRequest*C\n\x08InputKey\x12\x06\n\x02UP\x10\x00\x12\x08\n\x04DOWN\x10\x01\x12\t\n\x05RIGHT\x10\x02\x12\x08\n\x04LEFT\x10\x03\x12\x06\n\x02OK\x10\x04\x12\x08\n\x04BACK\x10\x05*D\n\tInputType\x12\t\n\x05PRESS\x10\x00\x12\x0b\n\x07RELEASE\x10\x01\x12\t\n\x05SHORT\x10\x02\x12\x08\n\x04LONG\x10\x03\x12\n\n\x06REPEAT\x10\x04*Y\n\x11ScreenOrientation\x12\x0e\n\nHORIZONTAL\x10\x00\x12\x13\n\x0fHORIZONTAL_FLIP\x10\x01\x12\x0c\n\x08VERTICAL\x10\x02\x12\x11\n\rVERTICAL_FLIP\x10\x03B$\n"com.flipperdevices.protobuf.screenb\x06proto3') 8 | _globals = globals() 9 | _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) 10 | _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'gui_pb2', _globals) 11 | if _descriptor._USE_C_DESCRIPTORS == False: 12 | _globals['DESCRIPTOR']._options = None 13 | _globals['DESCRIPTOR']._serialized_options = b'\n"com.flipperdevices.protobuf.screen' 14 | _globals['_INPUTKEY']._serialized_start = 363 15 | _globals['_INPUTKEY']._serialized_end = 430 16 | _globals['_INPUTTYPE']._serialized_start = 432 17 | _globals['_INPUTTYPE']._serialized_end = 500 18 | _globals['_SCREENORIENTATION']._serialized_start = 502 19 | _globals['_SCREENORIENTATION']._serialized_end = 591 20 | _globals['_SCREENFRAME']._serialized_start = 21 21 | _globals['_SCREENFRAME']._serialized_end = 96 22 | _globals['_STARTSCREENSTREAMREQUEST']._serialized_start = 98 23 | _globals['_STARTSCREENSTREAMREQUEST']._serialized_end = 124 24 | _globals['_STOPSCREENSTREAMREQUEST']._serialized_start = 126 25 | _globals['_STOPSCREENSTREAMREQUEST']._serialized_end = 151 26 | _globals['_SENDINPUTEVENTREQUEST']._serialized_start = 153 27 | _globals['_SENDINPUTEVENTREQUEST']._serialized_end = 240 28 | _globals['_STARTVIRTUALDISPLAYREQUEST']._serialized_start = 242 29 | _globals['_STARTVIRTUALDISPLAYREQUEST']._serialized_end = 332 30 | _globals['_STOPVIRTUALDISPLAYREQUEST']._serialized_start = 334 31 | _globals['_STOPVIRTUALDISPLAYREQUEST']._serialized_end = 361 -------------------------------------------------------------------------------- /flipperzero_protobuf/flipperzero_protobuf_compiled/property_pb2.py: -------------------------------------------------------------------------------- 1 | """Generated protocol buffer code.""" 2 | from google.protobuf import descriptor as _descriptor 3 | from google.protobuf import descriptor_pool as _descriptor_pool 4 | from google.protobuf import symbol_database as _symbol_database 5 | from google.protobuf.internal import builder as _builder 6 | _sym_db = _symbol_database.Default() 7 | DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0eproperty.proto\x12\x0bPB_Property"\x19\n\nGetRequest\x12\x0b\n\x03key\x18\x01 \x01(\t")\n\x0bGetResponse\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\tB&\n$com.flipperdevices.protobuf.propertyb\x06proto3') 8 | _globals = globals() 9 | _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) 10 | _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'property_pb2', _globals) 11 | if _descriptor._USE_C_DESCRIPTORS == False: 12 | _globals['DESCRIPTOR']._options = None 13 | _globals['DESCRIPTOR']._serialized_options = b'\n$com.flipperdevices.protobuf.property' 14 | _globals['_GETREQUEST']._serialized_start = 31 15 | _globals['_GETREQUEST']._serialized_end = 56 16 | _globals['_GETRESPONSE']._serialized_start = 58 17 | _globals['_GETRESPONSE']._serialized_end = 99 -------------------------------------------------------------------------------- /flipperzero_protobuf/flipperzero_protobuf_compiled/storage_pb2.py: -------------------------------------------------------------------------------- 1 | """Generated protocol buffer code.""" 2 | from google.protobuf import descriptor as _descriptor 3 | from google.protobuf import descriptor_pool as _descriptor_pool 4 | from google.protobuf import symbol_database as _symbol_database 5 | from google.protobuf.internal import builder as _builder 6 | _sym_db = _symbol_database.Default() 7 | DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\rstorage.proto\x12\nPB_Storage"\x88\x01\n\x04File\x12\'\n\x04type\x18\x01 \x01(\x0e2\x19.PB_Storage.File.FileType\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x0c\n\x04size\x18\x03 \x01(\r\x12\x0c\n\x04data\x18\x04 \x01(\x0c\x12\x0e\n\x06md5sum\x18\x05 \x01(\t"\x1d\n\x08FileType\x12\x08\n\x04FILE\x10\x00\x12\x07\n\x03DIR\x10\x01"\x1b\n\x0bInfoRequest\x12\x0c\n\x04path\x18\x01 \x01(\t"7\n\x0cInfoResponse\x12\x13\n\x0btotal_space\x18\x01 \x01(\x04\x12\x12\n\nfree_space\x18\x02 \x01(\x04" \n\x10TimestampRequest\x12\x0c\n\x04path\x18\x01 \x01(\t"&\n\x11TimestampResponse\x12\x11\n\ttimestamp\x18\x01 \x01(\r"\x1b\n\x0bStatRequest\x12\x0c\n\x04path\x18\x01 \x01(\t".\n\x0cStatResponse\x12\x1e\n\x04file\x18\x01 \x01(\x0b2\x10.PB_Storage.File"I\n\x0bListRequest\x12\x0c\n\x04path\x18\x01 \x01(\t\x12\x13\n\x0binclude_md5\x18\x02 \x01(\x08\x12\x17\n\x0ffilter_max_size\x18\x03 \x01(\r".\n\x0cListResponse\x12\x1e\n\x04file\x18\x01 \x03(\x0b2\x10.PB_Storage.File"\x1b\n\x0bReadRequest\x12\x0c\n\x04path\x18\x01 \x01(\t".\n\x0cReadResponse\x12\x1e\n\x04file\x18\x01 \x01(\x0b2\x10.PB_Storage.File"<\n\x0cWriteRequest\x12\x0c\n\x04path\x18\x01 \x01(\t\x12\x1e\n\x04file\x18\x02 \x01(\x0b2\x10.PB_Storage.File"0\n\rDeleteRequest\x12\x0c\n\x04path\x18\x01 \x01(\t\x12\x11\n\trecursive\x18\x02 \x01(\x08"\x1c\n\x0cMkdirRequest\x12\x0c\n\x04path\x18\x01 \x01(\t"\x1d\n\rMd5sumRequest\x12\x0c\n\x04path\x18\x01 \x01(\t" \n\x0eMd5sumResponse\x12\x0e\n\x06md5sum\x18\x01 \x01(\t"3\n\rRenameRequest\x12\x10\n\x08old_path\x18\x01 \x01(\t\x12\x10\n\x08new_path\x18\x02 \x01(\t"+\n\x13BackupCreateRequest\x12\x14\n\x0carchive_path\x18\x01 \x01(\t",\n\x14BackupRestoreRequest\x12\x14\n\x0carchive_path\x18\x01 \x01(\t"7\n\x11TarExtractRequest\x12\x10\n\x08tar_path\x18\x01 \x01(\t\x12\x10\n\x08out_path\x18\x02 \x01(\tB%\n#com.flipperdevices.protobuf.storageb\x06proto3') 8 | _globals = globals() 9 | _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) 10 | _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'storage_pb2', _globals) 11 | if _descriptor._USE_C_DESCRIPTORS == False: 12 | _globals['DESCRIPTOR']._options = None 13 | _globals['DESCRIPTOR']._serialized_options = b'\n#com.flipperdevices.protobuf.storage' 14 | _globals['_FILE']._serialized_start = 30 15 | _globals['_FILE']._serialized_end = 166 16 | _globals['_FILE_FILETYPE']._serialized_start = 137 17 | _globals['_FILE_FILETYPE']._serialized_end = 166 18 | _globals['_INFOREQUEST']._serialized_start = 168 19 | _globals['_INFOREQUEST']._serialized_end = 195 20 | _globals['_INFORESPONSE']._serialized_start = 197 21 | _globals['_INFORESPONSE']._serialized_end = 252 22 | _globals['_TIMESTAMPREQUEST']._serialized_start = 254 23 | _globals['_TIMESTAMPREQUEST']._serialized_end = 286 24 | _globals['_TIMESTAMPRESPONSE']._serialized_start = 288 25 | _globals['_TIMESTAMPRESPONSE']._serialized_end = 326 26 | _globals['_STATREQUEST']._serialized_start = 328 27 | _globals['_STATREQUEST']._serialized_end = 355 28 | _globals['_STATRESPONSE']._serialized_start = 357 29 | _globals['_STATRESPONSE']._serialized_end = 403 30 | _globals['_LISTREQUEST']._serialized_start = 405 31 | _globals['_LISTREQUEST']._serialized_end = 478 32 | _globals['_LISTRESPONSE']._serialized_start = 480 33 | _globals['_LISTRESPONSE']._serialized_end = 526 34 | _globals['_READREQUEST']._serialized_start = 528 35 | _globals['_READREQUEST']._serialized_end = 555 36 | _globals['_READRESPONSE']._serialized_start = 557 37 | _globals['_READRESPONSE']._serialized_end = 603 38 | _globals['_WRITEREQUEST']._serialized_start = 605 39 | _globals['_WRITEREQUEST']._serialized_end = 665 40 | _globals['_DELETEREQUEST']._serialized_start = 667 41 | _globals['_DELETEREQUEST']._serialized_end = 715 42 | _globals['_MKDIRREQUEST']._serialized_start = 717 43 | _globals['_MKDIRREQUEST']._serialized_end = 745 44 | _globals['_MD5SUMREQUEST']._serialized_start = 747 45 | _globals['_MD5SUMREQUEST']._serialized_end = 776 46 | _globals['_MD5SUMRESPONSE']._serialized_start = 778 47 | _globals['_MD5SUMRESPONSE']._serialized_end = 810 48 | _globals['_RENAMEREQUEST']._serialized_start = 812 49 | _globals['_RENAMEREQUEST']._serialized_end = 863 50 | _globals['_BACKUPCREATEREQUEST']._serialized_start = 865 51 | _globals['_BACKUPCREATEREQUEST']._serialized_end = 908 52 | _globals['_BACKUPRESTOREREQUEST']._serialized_start = 910 53 | _globals['_BACKUPRESTOREREQUEST']._serialized_end = 954 54 | _globals['_TAREXTRACTREQUEST']._serialized_start = 956 55 | _globals['_TAREXTRACTREQUEST']._serialized_end = 1011 -------------------------------------------------------------------------------- /flipperzero_protobuf/flipperzero_protobuf_compiled/system_pb2.py: -------------------------------------------------------------------------------- 1 | """Generated protocol buffer code.""" 2 | from google.protobuf import descriptor as _descriptor 3 | from google.protobuf import descriptor_pool as _descriptor_pool 4 | from google.protobuf import symbol_database as _symbol_database 5 | from google.protobuf.internal import builder as _builder 6 | _sym_db = _symbol_database.Default() 7 | DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0csystem.proto\x12\tPB_System"\x1b\n\x0bPingRequest\x12\x0c\n\x04data\x18\x01 \x01(\x0c"\x1c\n\x0cPingResponse\x12\x0c\n\x04data\x18\x01 \x01(\x0c"m\n\rRebootRequest\x121\n\x04mode\x18\x01 \x01(\x0e2#.PB_System.RebootRequest.RebootMode")\n\nRebootMode\x12\x06\n\x02OS\x10\x00\x12\x07\n\x03DFU\x10\x01\x12\n\n\x06UPDATE\x10\x02"\x13\n\x11DeviceInfoRequest"0\n\x12DeviceInfoResponse\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t"\x15\n\x13FactoryResetRequest"\x14\n\x12GetDateTimeRequest"<\n\x13GetDateTimeResponse\x12%\n\x08datetime\x18\x01 \x01(\x0b2\x13.PB_System.DateTime";\n\x12SetDateTimeRequest\x12%\n\x08datetime\x18\x01 \x01(\x0b2\x13.PB_System.DateTime"s\n\x08DateTime\x12\x0c\n\x04hour\x18\x01 \x01(\r\x12\x0e\n\x06minute\x18\x02 \x01(\r\x12\x0e\n\x06second\x18\x03 \x01(\r\x12\x0b\n\x03day\x18\x04 \x01(\r\x12\r\n\x05month\x18\x05 \x01(\r\x12\x0c\n\x04year\x18\x06 \x01(\r\x12\x0f\n\x07weekday\x18\x07 \x01(\r"\x1d\n\x1bPlayAudiovisualAlertRequest"\x18\n\x16ProtobufVersionRequest"7\n\x17ProtobufVersionResponse\x12\r\n\x05major\x18\x01 \x01(\r\x12\r\n\x05minor\x18\x02 \x01(\r"(\n\rUpdateRequest\x12\x17\n\x0fupdate_manifest\x18\x01 \x01(\t"\xca\x02\n\x0eUpdateResponse\x128\n\x04code\x18\x01 \x01(\x0e2*.PB_System.UpdateResponse.UpdateResultCode"\xfd\x01\n\x10UpdateResultCode\x12\x06\n\x02OK\x10\x00\x12\x17\n\x13ManifestPathInvalid\x10\x01\x12\x1a\n\x16ManifestFolderNotFound\x10\x02\x12\x13\n\x0fManifestInvalid\x10\x03\x12\x10\n\x0cStageMissing\x10\x04\x12\x17\n\x13StageIntegrityError\x10\x05\x12\x18\n\x14ManifestPointerError\x10\x06\x12\x12\n\x0eTargetMismatch\x10\x07\x12\x1b\n\x17OutdatedManifestVersion\x10\x08\x12\x0b\n\x07IntFull\x10\t\x12\x14\n\x10UnspecifiedError\x10\n"\x12\n\x10PowerInfoRequest"/\n\x11PowerInfoResponse\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\tB$\n"com.flipperdevices.protobuf.systemb\x06proto3') 8 | _globals = globals() 9 | _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) 10 | _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'system_pb2', _globals) 11 | if _descriptor._USE_C_DESCRIPTORS == False: 12 | _globals['DESCRIPTOR']._options = None 13 | _globals['DESCRIPTOR']._serialized_options = b'\n"com.flipperdevices.protobuf.system' 14 | _globals['_PINGREQUEST']._serialized_start = 27 15 | _globals['_PINGREQUEST']._serialized_end = 54 16 | _globals['_PINGRESPONSE']._serialized_start = 56 17 | _globals['_PINGRESPONSE']._serialized_end = 84 18 | _globals['_REBOOTREQUEST']._serialized_start = 86 19 | _globals['_REBOOTREQUEST']._serialized_end = 195 20 | _globals['_REBOOTREQUEST_REBOOTMODE']._serialized_start = 154 21 | _globals['_REBOOTREQUEST_REBOOTMODE']._serialized_end = 195 22 | _globals['_DEVICEINFOREQUEST']._serialized_start = 197 23 | _globals['_DEVICEINFOREQUEST']._serialized_end = 216 24 | _globals['_DEVICEINFORESPONSE']._serialized_start = 218 25 | _globals['_DEVICEINFORESPONSE']._serialized_end = 266 26 | _globals['_FACTORYRESETREQUEST']._serialized_start = 268 27 | _globals['_FACTORYRESETREQUEST']._serialized_end = 289 28 | _globals['_GETDATETIMEREQUEST']._serialized_start = 291 29 | _globals['_GETDATETIMEREQUEST']._serialized_end = 311 30 | _globals['_GETDATETIMERESPONSE']._serialized_start = 313 31 | _globals['_GETDATETIMERESPONSE']._serialized_end = 373 32 | _globals['_SETDATETIMEREQUEST']._serialized_start = 375 33 | _globals['_SETDATETIMEREQUEST']._serialized_end = 434 34 | _globals['_DATETIME']._serialized_start = 436 35 | _globals['_DATETIME']._serialized_end = 551 36 | _globals['_PLAYAUDIOVISUALALERTREQUEST']._serialized_start = 553 37 | _globals['_PLAYAUDIOVISUALALERTREQUEST']._serialized_end = 582 38 | _globals['_PROTOBUFVERSIONREQUEST']._serialized_start = 584 39 | _globals['_PROTOBUFVERSIONREQUEST']._serialized_end = 608 40 | _globals['_PROTOBUFVERSIONRESPONSE']._serialized_start = 610 41 | _globals['_PROTOBUFVERSIONRESPONSE']._serialized_end = 665 42 | _globals['_UPDATEREQUEST']._serialized_start = 667 43 | _globals['_UPDATEREQUEST']._serialized_end = 707 44 | _globals['_UPDATERESPONSE']._serialized_start = 710 45 | _globals['_UPDATERESPONSE']._serialized_end = 1040 46 | _globals['_UPDATERESPONSE_UPDATERESULTCODE']._serialized_start = 787 47 | _globals['_UPDATERESPONSE_UPDATERESULTCODE']._serialized_end = 1040 48 | _globals['_POWERINFOREQUEST']._serialized_start = 1042 49 | _globals['_POWERINFOREQUEST']._serialized_end = 1060 50 | _globals['_POWERINFORESPONSE']._serialized_start = 1062 51 | _globals['_POWERINFORESPONSE']._serialized_end = 1109 -------------------------------------------------------------------------------- /flipperzero_protobuf/rebuild.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | protoc -I=./flipperzero-protobuf --python_out=./flipperzero_protobuf_compiled ./flipperzero-protobuf/*.proto 4 | protol --create-package --in-place --python-out ./flipperzero_protobuf_compiled protoc --proto-path ./flipperzero-protobuf ./flipperzero-protobuf/*.proto -------------------------------------------------------------------------------- /flipperzero_protobuf/version.py: -------------------------------------------------------------------------------- 1 | """Version Info""" 2 | 3 | __version__ = "0.1.20221108" 4 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | 2 | [build-system] 3 | requires = ["setuptools>=61.0"] 4 | build-backend = "setuptools.build_meta" 5 | 6 | 7 | [project] 8 | name = "flipperzero_protobuf" 9 | keywords = ["flipperzero", "protobuf"] 10 | #version = "0.1.20220806" 11 | authors = [ 12 | { name="flipper devices", email="hello@flipperzero.one" }, 13 | { name="peter shipley", email="peter.shipley@gmail.com" }, 14 | ] 15 | description = "Python bindings for Flipper Zero protobuf protocol" 16 | 17 | dependencies = [ 18 | "numpy==1.21.5", 19 | "protobuf==4.21.3", 20 | "pyserial", 21 | #"protoletariat" 22 | ] 23 | readme = "README.md" 24 | dynamic = [ "entry-points", "version"] 25 | # license = { file="LICENSE" } 26 | license = {text = "BSD 3-Clause License"} 27 | requires-python = ">=3.7" 28 | classifiers = [ 29 | "Programming Language :: Python :: 3", 30 | "License :: OSI Approved :: BSD License", 31 | "Operating System :: OS Independent", 32 | "Requires-Dist: protoletariat", 33 | "Requires-Dist: pyserial", 34 | "Requires-Dist: numpy (>=3.21.5)", 35 | "Requires-Dist: protobuf (>=3.12.4)", 36 | ] 37 | 38 | [console_scripts] 39 | flipperzero_cmd = "flipperzero_protobuf.flipperzero_cmd:main" 40 | 41 | [project.urls] 42 | "Homepage" = "https://github.com/flipperdevices/flipperzero_protobuf_py" 43 | "Bug Tracker" = "https://github.com/flipperdevices/flipperzero_protobuf_py/issues" 44 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | numpy==1.22.3 2 | protobuf==3.20.2 3 | protoletariat 4 | Pillow 5 | isort 6 | black @ git+https://github.com/psf/black 7 | pyserial 8 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import os.path 2 | import ssl 3 | from distutils.command.install_scripts import install_scripts 4 | 5 | from setuptools import find_packages, setup 6 | 7 | # from version import tag_version 8 | 9 | # ssl._create_default_https_context = ssl._create_unverified_context 10 | # python setup.py sdist -k -v --dry-run 11 | 12 | # python setup.py --dry-run --verbose install 13 | # python setup.py install --record files.txt 14 | 15 | # from distutils.core import setup 16 | 17 | version = "0.1.20221108" 18 | 19 | exec(open("./flipperzero_protobuf/version.py").read()) 20 | 21 | setup( 22 | name="flipperzero_protobuf", 23 | version=version, 24 | author="Flipper & Community", 25 | author_email="peter.shipley@gmail.com, hello@flipperzero.one", 26 | packages=[ 27 | "flipperzero_protobuf", 28 | "flipperzero_protobuf/flipperCmd", 29 | "flipperzero_protobuf/flipperzero_protobuf_compiled", 30 | ], 31 | url="https://github.com/evilpete/flipperzero_protobuf_py", 32 | git="https://github.com/evilpete/flipperzero_protobuf_py.git", 33 | classifiers=[ 34 | # How mature is this project? Common values are 35 | # 3 - Alpha 36 | # 4 - Beta 37 | # 5 - Production/Stable 38 | "Development Status :: 3 - Alpha", 39 | # Indicate who your project is intended for 40 | "Intended Audience :: Developers", 41 | "Topic :: Software Development :: Build Tools", 42 | "License :: OSI Approved :: BSD License", 43 | # Specify the Python versions you support here. In particular, ensure 44 | # that you indicate whether you support Python 2, Python 3 or both. 45 | "Programming Language :: Python :: 3", 46 | ], 47 | python_requires=">=3", 48 | license="BSD", 49 | # download_url='https://github.com/flipperdevices/flipperzero_protobuf_py/archive/refs/heads/main.zip', 50 | description="Python API wrapper for flipperzero_protobuf.", 51 | # long_description=open('README.txt').read(), 52 | # cmdclass = { 'install_scripts': install_scripts_and_symlinks } 53 | install_requires=[ 54 | 'pyreadline; platform_system == "Windows"', 55 | "numpy==1.22.3", 56 | "protobuf==3.20.2", 57 | "pyserial", 58 | ], 59 | entry_points={ 60 | "console_scripts": [ 61 | "flipperCmd = flipperzero_protobuf.flipperCmd.flipperzero_cmd:main" 62 | ], 63 | }, 64 | ) 65 | -------------------------------------------------------------------------------- /test_cmd.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | #!/usr/local/bin/python3 3 | # -*- coding: utf-8 -*- 4 | import re 5 | import sys 6 | 7 | from flipperzero_protobuf.flipperCmd.flipperzero_cmd import main 8 | 9 | if __name__ == "__main__": 10 | sys.argv[0] = re.sub(r"(-script\.pyw|\.exe)?$", "", sys.argv[0]) 11 | sys.exit(main()) 12 | --------------------------------------------------------------------------------