├── .gitignore ├── .gitmodules ├── CHANGELOG.md ├── LICENSE.txt ├── Makefile ├── README.md ├── application.fam ├── assets ├── file-browser.png ├── qflipper.png └── tic-tac-toe.png ├── docs ├── CHANGELOG.md ├── README.md ├── file-browser.png ├── pages │ ├── assets │ │ ├── adc_circuit.svg │ │ ├── favicon.png │ │ ├── gpio_interrupt_circuit.svg │ │ ├── logo.png │ │ ├── pwm_circuit.svg │ │ ├── pwm_signal.txt │ │ ├── repl.gif │ │ └── repl.yml │ ├── changelog.rst │ ├── conf.py │ ├── examples.rst │ ├── features.md │ ├── index.rst │ ├── license.rst │ ├── quickstart.rst │ ├── reference.rst │ └── roadmap.md ├── tic-tac-toe.png └── welcome.png ├── examples ├── dict_test.py ├── flipperzero_adc_test.py ├── flipperzero_canvas_test.py ├── flipperzero_dialog_message_test.py ├── flipperzero_draw_on_input_test.py ├── flipperzero_gpio_input_test.py ├── flipperzero_gpio_interrupt_test.py ├── flipperzero_gpio_test.py ├── flipperzero_infrared_test.py ├── flipperzero_input_test.py ├── flipperzero_light_test.py ├── flipperzero_pwm_test.py ├── flipperzero_speaker_note_test.py ├── flipperzero_speaker_test.py ├── flipperzero_vibro_test.py ├── gc_test.py ├── greeter_test.py ├── hello_test.py ├── import_error_test.py ├── import_test.py ├── infrared_signal_viewer.py ├── logger.py ├── open.py ├── overflow_test.py ├── raise_test.py ├── rand_test.py ├── sleep_test.py ├── tic_tac_toe.py ├── try_except_test.py └── uart.py ├── flipper.json ├── flipperzero ├── README.md ├── _adc.py ├── _canvas.py ├── _dialog.py ├── _gpio.py ├── _infrared.py ├── _input.py ├── _light.py ├── _pwm.py ├── _speaker.py ├── _uart.py ├── _vibro.py ├── io.py ├── logging.py └── random.py ├── icon.png ├── images ├── ButtonCenter_7x7.png ├── Pin_back_arrow_10x8.png ├── qrcode.png └── splash.png ├── lib └── micropython-port │ ├── mp_flipper_context.h │ ├── mp_flipper_file_helper.c │ ├── mp_flipper_file_helper.h │ ├── mp_flipper_file_reader.c │ ├── mp_flipper_fileio.c │ ├── mp_flipper_halport.c │ ├── mp_flipper_logging.c │ ├── mp_flipper_modflipperzero_adc.c │ ├── mp_flipper_modflipperzero_canvas.c │ ├── mp_flipper_modflipperzero_dialog.c │ ├── mp_flipper_modflipperzero_gpio.c │ ├── mp_flipper_modflipperzero_infrared.c │ ├── mp_flipper_modflipperzero_light.c │ ├── mp_flipper_modflipperzero_pwm.c │ ├── mp_flipper_modflipperzero_speaker.c │ ├── mp_flipper_modflipperzero_uart.c │ ├── mp_flipper_modflipperzero_vibro.c │ ├── mp_flipper_modrandom.c │ ├── mp_flipper_modtime.c │ ├── mp_flipper_polyfill.c │ └── mp_flipper_runtime.c ├── logo.svg ├── package-lock.json ├── package.json ├── publish.sh ├── pyproject.toml ├── requirements.txt ├── splash.svg ├── upython.c ├── upython.h ├── upython_cli.c ├── upython_file.c ├── upython_repl.c └── upython_splash.c /.gitignore: -------------------------------------------------------------------------------- 1 | /dist/ 2 | /venv/ 3 | /node_modules/ 4 | /flipperzero/__init__.py 5 | /fssdk 6 | .vscode 7 | .clang-format 8 | .clangd 9 | .editorconfig 10 | .env 11 | .ufbt 12 | __pycache__ -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "lib/micropython"] 2 | path = lib/micropython 3 | url = https://github.com/ofabel/mp-flipper.git 4 | branch = lib-release-fap 5 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [Unreleased] 9 | 10 | ## [1.8.0] 11 | 12 | ### Fixed 13 | 14 | * [#9](https://github.com/ofabel/mp-flipper/issues/9): Update to latest SDK version. 15 | 16 | ## [1.7.0] 17 | 18 | ### Changed 19 | 20 | * The `SPEAKER_NOTE_*` constants are replaced by an attribute delegator function to save space. 21 | 22 | ### Fixed 23 | 24 | * [#6](https://github.com/ofabel/mp-flipper/issues/6): Update to latest SDK version. 25 | 26 | ## [1.6.0] - 2024-11-17 27 | 28 | ### Added 29 | 30 | * Enabled extra functions for the `random` module. 31 | 32 | ## [1.5.0] - 2024-10-06 33 | 34 | ### Added 35 | 36 | * Support for basic file system operations using the `io` module: 37 | * Read and write files. 38 | * Open in text or binary mode. 39 | * Simple `logging` module: 40 | * Log levels according to the Flipper Zero API: trace, debug, info, warn, error. 41 | * Only the root logger is supported, so no `getLogger` function. 42 | * Logs directly to the log output, so no output in the REPL. 43 | * Redirect output of `print` statements: 44 | * To `stdout` when a script is invoked by `py` command from the CLI. 45 | * To the log buffer, if a script is invoked from the UI. 46 | * UART support for the `flipperzero` module. 47 | 48 | ### Changed 49 | 50 | * The `py` command waits until the script terminates. 51 | 52 | ### Fixed 53 | 54 | * [#3](https://github.com/ofabel/mp-flipper/issues/3): Proper `CR` and `LF` handling in the REPL. 55 | 56 | ## [1.4.0] - 2024-09-29 57 | 58 | ### Added 59 | 60 | * Allow passing the path to the script to execute as a CLI argument. 61 | * Open a REPL from the CLI interface by using the `py` command: 62 | * The `py` command is only available while the app is running. 63 | * You cannot run a Python script and use the REPL at the same time. 64 | * You can also start a Python script with the `py` command while the app is idle. 65 | 66 | ### Changed 67 | 68 | * MicroPython update to version 1.23.0. 69 | 70 | ## [1.3.0] - 2024-09-08 71 | 72 | ### Added 73 | 74 | * Simple ADC support for the `flipperzero` module: 75 | * Read raw value. 76 | * Read voltage. 77 | * Simple PWM support for the `flipperzero` module: 78 | * Start a signal. 79 | * Stop a signal. 80 | * Check the status. 81 | * Infrared support for the `flipperzero` module: 82 | * Receive a signal. 83 | * Transmit a signal. 84 | * Check the status. 85 | * Reset used GPIO pins upon script termination. 86 | * Improved GPIO related functions to prevent user errors. 87 | * Published [Python package on PyPI](https://pypi.org/project/flipperzero/) for code completion support. 88 | 89 | ### Changed 90 | 91 | * The GPIO init function `flipperzero.gpio_init_pin` returns a boolean value. 92 | 93 | ## [1.2.0] - 2024-09-05 94 | 95 | ### Added 96 | 97 | * Constants for all musical notes from C0 up to B8. 98 | * Constants for minimum and maximum speaker volumes. 99 | * Simple GPIO support for the `flipperzero` module: 100 | * Initialize a pin. 101 | * Read from a pin. 102 | * Write to a pin. 103 | * Handle interrupts. 104 | 105 | ### Fixed 106 | 107 | * Message box alignment parameters `h` and `v` are now correctly evaluated. 108 | 109 | ## [1.1.0] - 2024-08-28 110 | 111 | ### Added 112 | 113 | * Display splash screen upon application start. 114 | * API documentation on [GitHub pages](https://ofabel.github.io/mp-flipper/). 115 | 116 | ## [1.0.0] - 2024-08-22 117 | 118 | ### Added 119 | 120 | * First stable release on the [application catalog](https://github.com/flipperdevices/flipper-application-catalog). 121 | 122 | ### Changed 123 | 124 | * Application ID is now `upython` 125 | 126 | ## [0.5.0-beta.1] - 2024-08-04 127 | 128 | ### Added 129 | 130 | * Message dialog support. 131 | * Update to the latest 0.104.0 firmware. 132 | 133 | ### Removed 134 | 135 | * Disabled various Python builtins to shrink binary size. 136 | 137 | ## [0.4.0-beta.1] - 2024-04-14 138 | 139 | ### Added 140 | 141 | * [Library](https://github.com/ofabel/mp-flipper/tree/lib) to include in the [firmware repository](https://github.com/ofabel/flipperzero-firmware). 142 | * All generated files from the build prozess are now [part of the repository](https://github.com/ofabel/mp-flipper/tree/lib-release). 143 | * Enabled split heap support for MicroPython: 144 | * The runtime can allocate and free heap memory. 145 | * Allows to start the Python process with small heap. 146 | * Enabled scheduler support (required for interrupt handling). 147 | * Enabled support for module `__init__` functions. 148 | * Stabilized `flipperzero` module API: 149 | * Canvas support has now a proper implementation. 150 | * Interrupts from buttons are supported. 151 | 152 | ## [0.3.0-alpha.1] - 2024-04-04 153 | 154 | ### Added 155 | 156 | * Floating point support 157 | * Extend `flipperzero` module with support for: 158 | * Speaker, set volume and frequency 159 | * Canvas, very wacky implementation 160 | 161 | ## [0.2.0-alpha.1] - 2024-04-03 162 | 163 | ### Added 164 | 165 | * Support for external imports 166 | * Python `time` module support 167 | * Python `random` module support 168 | * Basic `flipperzero` module with support for: 169 | * Vibration 170 | * LED 171 | * Backlight 172 | * Some test Python scripts 173 | 174 | ## [0.1.0-alpha.1] - 2024-04-01 175 | 176 | ### Added 177 | 178 | * Basic build setup 179 | * Minimal working example 180 | 181 | [Unreleased]: https://github.com/ofabel/mp-flipper/compare/v1.8.0...dev 182 | [1.8.0]: https://github.com/ofabel/mp-flipper/compare/v1.7.0...v1.8.0 183 | [1.7.0]: https://github.com/ofabel/mp-flipper/compare/v1.6.0...v1.7.0 184 | [1.6.0]: https://github.com/ofabel/mp-flipper/compare/v1.5.0...v1.6.0 185 | [1.5.0]: https://github.com/ofabel/mp-flipper/compare/v1.4.0...v1.5.0 186 | [1.4.0]: https://github.com/ofabel/mp-flipper/compare/v1.3.0...v1.4.0 187 | [1.3.0]: https://github.com/ofabel/mp-flipper/compare/v1.2.0...v1.3.0 188 | [1.2.0]: https://github.com/ofabel/mp-flipper/compare/v1.1.0...v1.2.0 189 | [1.1.0]: https://github.com/ofabel/mp-flipper/compare/v1.0.0...v1.1.0 190 | [1.0.0]: https://github.com/ofabel/mp-flipper/compare/v0.5.0-beta.1...v1.0.0 191 | [0.5.0-beta.1]: https://github.com/ofabel/mp-flipper/compare/v0.4.0-beta.1...v0.5.0-beta.1 192 | [0.4.0-beta.1]: https://github.com/ofabel/mp-flipper/compare/v0.3.0-alpha.1...v0.4.0-beta.1 193 | [0.3.0-alpha.1]: https://github.com/ofabel/mp-flipper/compare/v0.2.0-alpha.1...v0.3.0-alpha.1 194 | [0.2.0-alpha.1]: https://github.com/ofabel/mp-flipper/compare/v0.1.0-alpha.1...v0.2.0-alpha.1 195 | [0.1.0-alpha.1]: https://github.com/ofabel/mp-flipper/releases/tag/v0.1.0-alpha.1 196 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | =========== 3 | 4 | Copyright (c) 2024 Oliver Fabel 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: update 2 | update: 3 | git submodule update --remote lib/micropython && git add lib/micropython 4 | 5 | .PHONY: build-fap 6 | build-fap: update 7 | ufbt build 8 | 9 | .PHONY: launch 10 | launch: build-fap 11 | ufbt launch 12 | 13 | .PHONY: clean 14 | clean: 15 | ufbt -c 16 | 17 | .PHONY: build-pages 18 | build-pages: 19 | rm -rf ./dist/pages ./flipperzero/__init__.py 20 | cat ./flipperzero/_*.py > ./flipperzero/__init__.py 21 | source venv/bin/activate && sphinx-build docs/pages dist/pages 22 | 23 | .PHONY: publish-pages 24 | publish-pages: build-pages 25 | ./publish.sh pages 26 | 27 | .PHONY: build-python 28 | build-python: 29 | source venv/bin/activate && hatch build 30 | 31 | .PHONY: publish-python 32 | publish-python: build-python 33 | source venv/bin/activate && hatch publish dist/python/* 34 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![License](https://img.shields.io/github/license/ofabel/mp-flipper) 2 | ![Version](https://img.shields.io/github/v/tag/ofabel/mp-flipper) 3 | ![](https://img.shields.io/github/issues/ofabel/mp-flipper) 4 | 5 | # MicroPython Flipper Zero 6 | 7 | Allows you to use the power of Python natively on your Flipper Zero. 8 | The application is available on the official [Flipper Lab](https://lab.flipper.net/apps/upython). 9 | For details on how to programm your Flipper with Python, check out the [documentation](https://ofabel.github.io/mp-flipper/) on GitHub pages. 10 | 11 | ![MicroPython REPL](./docs/pages/assets/repl.gif) 12 | 13 | ## Disclaimer 14 | 15 | This FAP version requires about 80 kB from SRAM to start (needed for the Python runtime and compiler). 16 | Due to memory fragmentation it's possible, that the application crashes when you start it. 17 | If this happens, just try again (the crash doesn't harm your device). 18 | 19 | > [!IMPORTANT] 20 | > This problem is already addressed to the firmware developers in [this issue](https://github.com/flipperdevices/flipperzero-firmware/issues/3927). 21 | > Nevertheless, running the uPython application from the SD card is still a heavy task for the Flipper. 22 | 23 | _I'm thinking about publishing a fork of the original firmware with uPython bundled as a core service._ 24 | _This would mitigate all the memory problems._ 25 | _The SD card version would still be there and maintained, but possibly with a limited set of features._ 26 | 27 | ## Development 28 | 29 | This section is only relevant, if you want to build the FAP on your own. 30 | 31 | ### Branches 32 | 33 | This branch contains the [FAP](https://developer.flipper.net/flipperzero/doxygen/apps_on_sd_card.html) version. 34 | The results of the preceding research phase is still available in the [poc](https://github.com/ofabel/mp-flipper/tree/poc) branch. 35 | The [lib](https://github.com/ofabel/mp-flipper/tree/lib) branch of this repository contains just the [MicroPython](https://github.com/micropython/micropython) library. 36 | The progress of further research on what can be achieved when moving functionality to the firmware can be found in the [fork of the original firmware](https://github.com/ofabel/flipperzero-firmware/tree/ofa/micropython). 37 | 38 | ### Requirements 39 | 40 | * [Git](https://git-scm.com/) 41 | * [Make](https://www.gnu.org/software/make/) 42 | * [uFBT](https://pypi.org/project/ufbt/) available in your `PATH` (or you have to adjust the [Makefile](./Makefile)) 43 | * [Flipper Zero](https://flipperzero.one/) 44 | 45 | ### Setup 46 | 47 | ```bash 48 | git clone --recurse-submodules git@github.com:ofabel/mp-flipper.git 49 | ``` 50 | 51 | ### Build 52 | 53 | Just open a terminal and run the Makefile targets: 54 | 55 | ```bash 56 | make build 57 | ``` 58 | 59 | You can also build an launch the application on the attached Flipper Zero device in one command: 60 | 61 | ```bash 62 | make launch 63 | ``` 64 | -------------------------------------------------------------------------------- /application.fam: -------------------------------------------------------------------------------- 1 | App( 2 | appid="upython", 3 | name="uPython", 4 | apptype=FlipperAppType.EXTERNAL, 5 | entry_point="upython", 6 | stack_size=4 * 1024, 7 | fap_category="Tools", 8 | fap_version="1.8", 9 | fap_description="Compile and execute MicroPython scripts", 10 | fap_icon="icon.png", 11 | fap_icon_assets="images", 12 | fap_author="Oliver Fabel", 13 | fap_file_assets="examples", 14 | fap_weburl="https://github.com/ofabel/mp-flipper", 15 | sources=[ 16 | "*.c*", 17 | "!./lib/micropython", 18 | "!./lib/micropython-port", 19 | "!./flipperzero", 20 | "!./node_modules", 21 | "!./examples", 22 | "!./assets", 23 | "!./docs", 24 | "!./venv", 25 | "!./dist", 26 | ], 27 | fap_private_libs=[ 28 | Lib( 29 | name="micropython", 30 | cflags=[ 31 | "-Wno-error", 32 | "-w", 33 | # 34 | # required for floating point support 35 | # 36 | "-mcpu=cortex-m4", 37 | "-mfloat-abi=hard", 38 | "-mfpu=fpv4-sp-d16", 39 | "-mthumb", 40 | "-fsingle-precision-constant", 41 | "-fno-math-errno", 42 | ], 43 | cincludes=["."] 44 | ), 45 | Lib( 46 | name="micropython-port", 47 | cflags=[ 48 | "-Wno-error", 49 | "-w", 50 | # 51 | # required for floating point support 52 | # 53 | "-mcpu=cortex-m4", 54 | "-mfloat-abi=hard", 55 | "-mfpu=fpv4-sp-d16", 56 | "-mthumb", 57 | "-fsingle-precision-constant", 58 | "-fno-math-errno", 59 | ], 60 | cincludes=["."] 61 | ), 62 | ] 63 | ) 64 | -------------------------------------------------------------------------------- /assets/file-browser.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ofabel/mp-flipper/602080eca907cad035f570541384137d786ad52a/assets/file-browser.png -------------------------------------------------------------------------------- /assets/qflipper.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ofabel/mp-flipper/602080eca907cad035f570541384137d786ad52a/assets/qflipper.png -------------------------------------------------------------------------------- /assets/tic-tac-toe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ofabel/mp-flipper/602080eca907cad035f570541384137d786ad52a/assets/tic-tac-toe.png -------------------------------------------------------------------------------- /docs/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 1.8 2 | 3 | * Update to latest SDK version. 4 | 5 | ## 1.7 6 | 7 | * Replaced speaker note constants with an attribute delegator function to save space. 8 | * Update to latest SDK version. 9 | 10 | ## 1.6 11 | 12 | * Added extra functions for the **random** module. 13 | 14 | ## 1.5 15 | 16 | * Added **io** module for basic file system operations. 17 | * Added **logging** module to allow level based log messages. 18 | * Rework of the **print** function: output redirection, based on script invocation. 19 | * Added UART support: connect, read and write. 20 | * Fixed the line feed handling in the REPL. 21 | 22 | ## 1.4 23 | 24 | * Added interactive Python shell (aka REPL) as a CLI command. 25 | * Allow passing a path to a script to execute as a CLI argument. 26 | * Updated MicroPython to version 1.23.0. 27 | 28 | ## 1.3 29 | 30 | * Added simple ADC support: read value and voltage. 31 | * Added simple PWM support: start, stop, check status. 32 | * Added infrared support: receive and transmit a signal, check status. 33 | * Added success indicator to GPIO init function. 34 | * Reset used GPIO pins upon script termination. 35 | * Improved GPIO related functions to prevent user errors. 36 | * Published Python package on PyPI for code completion support. 37 | 38 | ## 1.2 39 | 40 | * Added simple GPIO support: initialize, read, write, interrupts. 41 | * Added constants for musical note frequencies from C0 up to B8. 42 | * Some minor fixes in the dialog functions. 43 | 44 | ## 1.1 45 | 46 | * Display splash screen upon application start. 47 | * API documentation available on GitHub pages. 48 | 49 | ## 1.0 50 | 51 | * Initial stable release. 52 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # MicroPython 2 | 3 | MicroPython is a version of the popular Python programming language, especially tailored for the hardware requirements of small microcontrollers, like the Flipper Zero. 4 | Instead of supporting the full standard library, only a subset is implemented, carefully selected with the requirements of a microcontroller environment in mind. 5 | But this should not limit your creativity and ability to create great applications at all. 6 | 7 | ## API 8 | 9 | Visit the repository website on GitHub or scan the QR code on the welcome screen. 10 | 11 | ## Disclaimer 12 | 13 | Running MicroPython is a heavy task for the Flipper. 14 | This does sometimes lead to an _Out of Memory_ error. 15 | **This doesn't harm your Flipper.** 16 | -------------------------------------------------------------------------------- /docs/file-browser.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ofabel/mp-flipper/602080eca907cad035f570541384137d786ad52a/docs/file-browser.png -------------------------------------------------------------------------------- /docs/pages/assets/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ofabel/mp-flipper/602080eca907cad035f570541384137d786ad52a/docs/pages/assets/favicon.png -------------------------------------------------------------------------------- /docs/pages/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ofabel/mp-flipper/602080eca907cad035f570541384137d786ad52a/docs/pages/assets/logo.png -------------------------------------------------------------------------------- /docs/pages/assets/pwm_signal.txt: -------------------------------------------------------------------------------- 1 | ────┐ ┌──┐ ┌────┐ ┌──┐ ┌────┐ ┌ ⋅ ⋅ ⋅ 2 | │ │ │ │ │ │ │ │ │ │ 3 | └───────┘ └────┘ └──────┘ └──────────────┘ └──────┘ 4 | 5 | 40 | 70 |20| 40 | 40 | 60 |20| 140 | 40 | 60 | ⋅ ⋅ ⋅ 6 | -------------------------------------------------------------------------------- /docs/pages/assets/repl.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ofabel/mp-flipper/602080eca907cad035f570541384137d786ad52a/docs/pages/assets/repl.gif -------------------------------------------------------------------------------- /docs/pages/assets/repl.yml: -------------------------------------------------------------------------------- 1 | # The configurations that used for the recording, feel free to edit them 2 | config: 3 | 4 | # Specify a command to be executed 5 | # like `/bin/bash -l`, `ls`, or any other commands 6 | # the default is bash for Linux 7 | # or powershell.exe for Windows 8 | command: bash -l 9 | 10 | # Specify the current working directory path 11 | # the default is the current working directory path 12 | cwd: ~/dev/scripts 13 | 14 | # Export additional ENV variables 15 | env: 16 | recording: true 17 | 18 | # Explicitly set the number of columns 19 | # or use `auto` to take the current 20 | # number of columns of your shell 21 | cols: 80 22 | 23 | # Explicitly set the number of rows 24 | # or use `auto` to take the current 25 | # number of rows of your shell 26 | rows: 17 27 | 28 | # Amount of times to repeat GIF 29 | # If value is -1, play once 30 | # If value is 0, loop indefinitely 31 | # If value is a positive number, loop n times 32 | repeat: 0 33 | 34 | # Quality 35 | # 1 - 100 36 | quality: 100 37 | 38 | # Delay between frames in ms 39 | # If the value is `auto` use the actual recording delays 40 | frameDelay: auto 41 | 42 | # Maximum delay between frames in ms 43 | # Ignored if the `frameDelay` isn't set to `auto` 44 | # Set to `auto` to prevent limiting the max idle time 45 | maxIdleTime: 2000 46 | 47 | # The surrounding frame box 48 | # The `type` can be null, window, floating, or solid` 49 | # To hide the title use the value null 50 | # Don't forget to add a backgroundColor style with a null as type 51 | frameBox: 52 | type: floating 53 | title: null 54 | style: 55 | border: 0px black solid 56 | # boxShadow: none 57 | # margin: 0px 58 | 59 | # Add a watermark image to the rendered gif 60 | # You need to specify an absolute path for 61 | # the image on your machine or a URL, and you can also 62 | # add your own CSS styles 63 | watermark: 64 | imagePath: null 65 | style: 66 | position: absolute 67 | right: 15px 68 | bottom: 15px 69 | width: 100px 70 | opacity: 0.9 71 | 72 | # Cursor style can be one of 73 | # `block`, `underline`, or `bar` 74 | cursorStyle: block 75 | 76 | # Font family 77 | # You can use any font that is installed on your machine 78 | # in CSS-like syntax 79 | fontFamily: "Monaco, Lucida Console, Ubuntu Mono, Monospace" 80 | 81 | # The size of the font 82 | fontSize: 12 83 | 84 | # The height of lines 85 | lineHeight: 1 86 | 87 | # The spacing between letters 88 | letterSpacing: 0 89 | 90 | # Theme 91 | theme: 92 | background: "transparent" 93 | foreground: "#afafaf" 94 | cursor: "#c7c7c7" 95 | black: "#232628" 96 | red: "#fc4384" 97 | green: "#b3e33b" 98 | yellow: "#ffa727" 99 | blue: "#75dff2" 100 | magenta: "#ae89fe" 101 | cyan: "#708387" 102 | white: "#d5d5d0" 103 | brightBlack: "#626566" 104 | brightRed: "#ff7fac" 105 | brightGreen: "#c8ed71" 106 | brightYellow: "#ebdf86" 107 | brightBlue: "#75dff2" 108 | brightMagenta: "#ae89fe" 109 | brightCyan: "#b1c6ca" 110 | brightWhite: "#f9f9f4" 111 | 112 | # Records, feel free to edit them 113 | records: 114 | - delay: 0 115 | content: "\r" 116 | - delay: 35 117 | content: "\r\r\n _.-------.._ -,\r\r\n .-\"```\"--..,,_/ /`-, -, \\ \r\r\n .:\" /:/ /'\\ \\ ,_..., `. | |\r\r\n / ,----/:/ /`\\ _\\~`_-\"` _;\r\r\n ' / /`\"\"\"'\\ \\ \\.~`_-' ,-\"'/ \r\r\n | | | 0 | | .-' ,/` /\r\r\n | ,..\\ \\ ,.-\"` ,/` /\r\r\n ; : `/`\"\"\\` ,/--==,/-----,\r\r\n | `-...| -.___-Z:_______J...---;\r\r\n : ` _-'\r\r\n _L_ _ ___ ___ ___ ___ ____--\"`___ _ ___\r\r\n| __|| | |_ _|| _ \\| _ \\| __|| _ \\ / __|| | |_ _|\r\r\n| _| | |__ | | | _/| _/| _| | / | (__ | |__ | |\r\r\n|_| |____||___||_| |_| |___||_|_\\ \\___||____||___|\r\r\n\r\r\nWelcome to Flipper Zero Command Line Interface!\r\r\nRead the manual: https://docs.flipper.net/development/cli\r\r\nRun `help` or `?` to list available commands\r\r\n\r\r\nFirmware version: 1.0.1 1.0.1 (fe424061 built on 10-09-2024)\r\r\n\r\r\n>: " 118 | - delay: 1259 119 | content: p 120 | - delay: 105 121 | content: 'y' 122 | - delay: 1112 123 | content: "\r\r\nMicroPython (v1.23.0, 2024-09-27) on Flipper Zero\r\r\nQuit: Ctrl+D | Heap: 4220 bytes | Stack: 2048 bytes\r\r\n To do a reboot, press Left+Back for 5 seconds.\r\r\nDocs: https://ofabel.github.io/mp-flipper\r\r\n" 124 | - delay: 5 125 | content: "\e[2K\r>>> " 126 | - delay: 2144 127 | content: p 128 | - delay: 195 129 | content: r 130 | - delay: 194 131 | content: i 132 | - delay: 53 133 | content: 'n' 134 | - delay: 55 135 | content: t 136 | - delay: 80 137 | content: ( 138 | - delay: 80 139 | content: '''' 140 | - delay: 100 141 | content: H 142 | - delay: 100 143 | content: e 144 | - delay: 134 145 | content: l 146 | - delay: 149 147 | content: l 148 | - delay: 254 149 | content: o 150 | - delay: 107 151 | content: ' ' 152 | - delay: 250 153 | content: f 154 | - delay: 161 155 | content: r 156 | - delay: 120 157 | content: o 158 | - delay: 112 159 | content: m 160 | - delay: 53 161 | content: ' ' 162 | - delay: 30 163 | content: R 164 | - delay: 104 165 | content: E 166 | - delay: 247 167 | content: P 168 | - delay: 256 169 | content: L 170 | - delay: 250 171 | content: '''' 172 | - delay: 250 173 | content: ) 174 | - delay: 541 175 | content: "\r\r\nHello from REPL\r\n\e[2K\r>>> " 176 | - delay: 3126 177 | content: i 178 | - delay: 82 179 | content: m 180 | - delay: 300 181 | content: p 182 | - delay: 300 183 | content: "\e[2K\r>>> import " 184 | - delay: 500 185 | content: f 186 | - delay: 82 187 | content: l 188 | - delay: 317 189 | content: "\e[2K\r>>> import flipperzero" 190 | - delay: 300 191 | content: ' ' 192 | - delay: 144 193 | content: a 194 | - delay: 75 195 | content: s 196 | - delay: 300 197 | content: ' ' 198 | - delay: 208 199 | content: f 200 | - delay: 121 201 | content: '0' 202 | - delay: 451 203 | content: "\r\r\n\e[2K\r>>> " 204 | - delay: 845 205 | content: f 206 | - delay: 75 207 | content: '0' 208 | - delay: 248 209 | content: . 210 | - delay: 307 211 | content: i 212 | - delay: 53 213 | content: 'n' 214 | - delay: 459 215 | content: "\e[2K\r>>> f0.infrared_" 216 | - delay: 1072 217 | content: "\r\ninfrared_is_busy infrared_receive\r\ninfrared_transmit\r\n\e[2K\r>>> f0.infrared_" 218 | - delay: 1169 219 | content: r 220 | - delay: 61 221 | content: e 222 | - delay: 984 223 | content: "\e[2K\r>>> f0.infrared_receive" 224 | - delay: 814 225 | content: ( 226 | - delay: 172 227 | content: ) 228 | - delay: 1262 229 | content: "\r\r\n" 230 | - delay: 2086 231 | content: "[9086, 4417, 636, 522, 611, 522, 610, 524, 608, 525, 608, 1635, 606, 527, 607, 527, 607, 527, 606, 527, 607, 528, 606, 527, 607, 528, 605, 1636, 606, 528, 606, 528, 606, 528, 606, 1636, 606, 528, 606, 528, 606, 1636, 605, 528, 606, 528, 606, 1636, 606, 528, 606, 528, 606, 1636, 605, 1636, 606, 528, 605, 1636, 606, 1636, 605, 528, 606, 1636, 605, 46392, 9057, 2206, 606, 95997, 9083, 2205, 607]\r\n\e[2K\r>>> " 232 | - delay: 3424 233 | content: "\e[2K\r>>> " 234 | -------------------------------------------------------------------------------- /docs/pages/changelog.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../../CHANGELOG.md 2 | :parser: myst_parser.sphinx_ -------------------------------------------------------------------------------- /docs/pages/conf.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import pathlib 3 | import sys 4 | 5 | base = pathlib.Path(__file__).parent.parent.parent 6 | root = base.__str__() 7 | flipperzero = base.joinpath('flipperzero').__str__() 8 | 9 | sys.path.append(root) 10 | sys.path.append(flipperzero) 11 | 12 | def copy_dict(source, target): 13 | for key, value in source.__dict__.items(): 14 | target.__dict__[key] = value 15 | 16 | import flipperzero.logging 17 | import logging 18 | 19 | copy_dict(flipperzero.logging, logging) 20 | 21 | import flipperzero.io 22 | import io 23 | 24 | copy_dict(flipperzero.io, io) 25 | 26 | import flipperzero.random 27 | import random 28 | 29 | copy_dict(flipperzero.random, random) 30 | 31 | now = datetime.datetime.now() 32 | 33 | project = 'uPython' 34 | copyright = str(now.year) + ', Oliver Fabel' 35 | author = 'Oliver Fabel' 36 | release = '1.8.0' 37 | version = '1.8' 38 | language = 'en' 39 | 40 | extensions = [ 41 | 'sphinx.ext.autodoc', 42 | 'myst_parser' 43 | ] 44 | source_suffix = { 45 | '.rst': 'restructuredtext', 46 | '.md': 'markdown' 47 | } 48 | 49 | templates_path = [ 50 | 'templates' 51 | ] 52 | exclude_patterns = [] 53 | include_patterns = [ 54 | '**' 55 | ] 56 | 57 | html_theme = 'alabaster' 58 | html_theme_options = { 59 | 'show_powered_by': False, 60 | 'extra_nav_links': { 61 | 'Source Code': 'https://www.github.com/ofabel/mp-flipper', 62 | 'Bugtracker': 'https://www.github.com/ofabel/mp-flipper/issues', 63 | 'Releases': 'https://lab.flipper.net/apps/upython' 64 | 65 | } 66 | } 67 | html_scaled_image_link = False 68 | html_copy_source = False 69 | html_show_copyright = False 70 | html_show_sphinx = False 71 | html_static_path = [ 72 | 'static' 73 | ] 74 | html_logo = 'assets/logo.png' 75 | html_favicon = 'assets/favicon.png' 76 | 77 | autodoc_default_options = { 78 | 'member-order': 'bysource', 79 | } 80 | 81 | add_module_names = True 82 | 83 | maximum_signature_line_length = 50 84 | -------------------------------------------------------------------------------- /docs/pages/examples.rst: -------------------------------------------------------------------------------- 1 | Examples 2 | ======== 3 | 4 | This page contains a few examples. 5 | See more on `GitHub `_. 6 | 7 | Speaker 8 | ------- 9 | 10 | .. literalinclude:: ../../examples/flipperzero_speaker_test.py 11 | :language: python 12 | 13 | For details, see the :ref:`reference-speaker` section on the :doc:`reference` page. 14 | 15 | Input 16 | ----- 17 | 18 | .. literalinclude:: ../../examples/flipperzero_draw_on_input_test.py 19 | :language: python 20 | 21 | For details, see the :ref:`reference-input` section on the :doc:`reference` page. 22 | 23 | Interrupts 24 | ---------- 25 | 26 | .. literalinclude:: ../../examples/flipperzero_gpio_interrupt_test.py 27 | :language: python 28 | 29 | This example drives an external LED upon interrupts: A rising edge on ``C0`` sets the pin ``A7`` to high, a rising edge on ``C1`` sets the pin ``A7`` to low. 30 | The following schematic circuit diagram shows the hardware setup for this example: 31 | 32 | .. figure:: ./assets/gpio_interrupt_circuit.svg 33 | :width: 90% 34 | 35 | Hardware setup for the GPIO interrupt example. 36 | 37 | For details, see the :ref:`reference-gpio` section on the :doc:`reference` page. 38 | 39 | ADC 40 | --- 41 | 42 | .. literalinclude:: ../../examples/flipperzero_adc_test.py 43 | :language: python 44 | 45 | This example uses a voltage divider with the 3.3 V source from pin 9. The switch ``S1`` changes the input voltage on ``C1`` between 0 and about 0.8 V. 46 | 47 | .. figure:: ./assets/adc_circuit.svg 48 | :width: 90% 49 | 50 | Hardware setup for the ADC example. 51 | 52 | For details, see the :ref:`reference-adc` section on the :doc:`reference` page. 53 | 54 | PWM 55 | --- 56 | 57 | .. literalinclude:: ../../examples/flipperzero_pwm_test.py 58 | :language: python 59 | 60 | This example drives an LED connected to pin ``A7`` and ``GND`` using a PWM signal with two different frequency and duty cycle settings. 61 | 62 | .. figure:: ./assets/pwm_circuit.svg 63 | :width: 90% 64 | 65 | Hardware setup for the PWM example. 66 | 67 | For details, see the :ref:`reference-pwm` section on the :doc:`reference` page. 68 | 69 | Infrared 70 | -------- 71 | 72 | .. literalinclude:: ../../examples/flipperzero_infrared_test.py 73 | :language: python 74 | 75 | For details, see the :ref:`reference-infrared` section on the :doc:`reference` page. 76 | -------------------------------------------------------------------------------- /docs/pages/features.md: -------------------------------------------------------------------------------- 1 | # Features 2 | 3 | Adding Python support to the Flipper Zero platform was only possible by rigorously sorting unnecessary language features. 4 | So here is a detailed list of all supported and unsupported Python language features. 5 | 6 | ## Supported 7 | 8 | The following features are enabled and supported by the interpreter: 9 | 10 | * Garbage collector is enabled. 11 | * Finaliser calls in the garbage collector (e.g. `__del__`). 12 | * The `__file__` constant. 13 | * Import of external files from the SD card. 14 | * Read and write files from and to the SD card. 15 | * The `time` module. 16 | * The `random` module. 17 | * The `logging` module. 18 | * The `io` module. 19 | * The `float` data type. 20 | * The `%` string formatting operator. 21 | * The `.format` string formatting function. 22 | * Support for [decorator](https://docs.python.org/3/glossary.html#term-decorator) functions. 23 | * The `setattr` function. 24 | * The `filter` function. 25 | * The `reversed` function. 26 | * The `min` and `max` function. 27 | * Module-level `__init__` imports. 28 | * Support for a [REPL](https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop). 29 | 30 | ## Unsupported 31 | 32 | The following features are disabled and _not_ supported by the interpreter: 33 | 34 | * The `__doc__` constants. 35 | * Source code line numbers in exceptions. 36 | * The `cmath` module. 37 | * The `complex` data type. 38 | * Support for multiple inheritance. 39 | * Module-level `__getattr__` support according to [PEP 562](https://peps.python.org/pep-0562/). 40 | * Support for the descriptors `__get__`, `__set__` and `__delete__`. 41 | * Coroutines with `async` and `await` functions. 42 | * The `:=` assign expression. 43 | * Non-standard `.pend_throw()` method for generators. 44 | * Support for `bytes.hex` and `bytes.fromhex`. 45 | * Support for unicode characters. 46 | * The string functions `.center`, `.count`, `.partition`, `.rpartition` and `.splitlines`. 47 | * The `bytearray` data type. 48 | * The `memoryview` data type. 49 | * The `slice` object. 50 | * The `frozenset` object. 51 | * The `property` decorator. 52 | * The `next` function with a second argument. 53 | * All special methods for user classes (e.g. `__imul__`). 54 | * The `enumerate` function. 55 | * The `compile` function. 56 | * Support for `eval`, `exec` and `execfile` functions. 57 | * The `NotImplemented` special constant. 58 | * The `input` function. 59 | * The `pow` function with 3 integer arguments. 60 | * The `help` function. 61 | * The `micropython` module. 62 | * The `array` module. 63 | * The `collections` module. 64 | * The `struct` module. 65 | * The `gc` module. 66 | * The `sys` module. 67 | * The `select` module. 68 | * The `json` module. 69 | 70 | This list of unsupported features is not set in stone. 71 | If you miss support for one particular feature, feel free to [open an issue](https://github.com/ofabel/mp-flipper/issues) or make a pull request. 72 | -------------------------------------------------------------------------------- /docs/pages/index.rst: -------------------------------------------------------------------------------- 1 | .. toctree:: 2 | :hidden: 3 | :maxdepth: 2 4 | 5 | quickstart 6 | reference 7 | features 8 | examples 9 | roadmap 10 | Changelog 11 | License 12 | 13 | MicroPython on Flipper Zero 14 | =========================== 15 | 16 | .. image:: https://img.shields.io/github/license/ofabel/mp-flipper 17 | :alt: License 18 | 19 | .. image:: https://img.shields.io/github/v/tag/ofabel/mp-flipper 20 | :alt: Version 21 | 22 | .. image:: https://img.shields.io/github/issues/ofabel/mp-flipper 23 | :alt: Issues 24 | 25 | A `MicroPython `_ port for the famous `Flipper Zero `_. 26 | No need to learn C: Use your favourite programming language to create apps, games and scripts. 27 | 28 | .. image:: ./assets/repl.gif 29 | :align: center 30 | 31 | Features 32 | -------- 33 | 34 | * Support for basic language constructs like functions, classes, loops, ... 35 | * Access the Flipper's hardware: buttons, speaker, LED, GPIO, ADC, PWM ... 36 | * No custom firmware is required, so no risk to brick your Flipper. 37 | 38 | A complete list can be found in the :doc:`features ` section. 39 | 40 | How to Start 41 | ------------ 42 | 43 | 1. Install the application from the `Flipper Lab `_ on your Flipper device. 44 | 2. Write some Python code or use one of the provided `examples `_. 45 | 3. Use the `qFlipper `_ application to upload the code to your Flipper's SD card. 46 | 4. Start the **uPython** application on your Flipper to execute your Python script. 47 | 48 | Checkout the :doc:`reference ` section for an in-depth API documentation. 49 | 50 | License 51 | ------- 52 | 53 | The uPython application is published under the :doc:`MIT ` license. 54 | 55 | -------------------------------------------------------------------------------- /docs/pages/license.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../../LICENSE.txt -------------------------------------------------------------------------------- /docs/pages/quickstart.rst: -------------------------------------------------------------------------------- 1 | Quickstart 2 | ========== 3 | 4 | This page provides some details on how to start and use the application. 5 | 6 | Basics 7 | ------ 8 | 9 | How to install the application and run a script: 10 | 11 | 1. Install the application from the `Flipper Lab `_ on your Flipper device. 12 | 2. Write some Python code or use one of the provided `examples `_. 13 | 3. Use the `qFlipper `_ application to upload the code to your Flipper's SD card. 14 | 4. Start the **uPython** application on your Flipper to run your Python script. 15 | 16 | Instead of running a script, you could also use the interactive MicroPython shell from the terminal. 17 | Visit the `Flipper documentation `_ for details about the CLI in general. 18 | 19 | .. hint:: 20 | 21 | Looking for a more efficient solution to copy your files or start a CLI session? 22 | Try the `Flipper Zero Script SDK `_ helper. 23 | 24 | In case your Flipper is not responding or a script doesn't behave as expected and won't finish: `do a reboot `_ by pressing and holding **Left** and **Back** for 5 seconds. 25 | 26 | Usage 27 | ----- 28 | 29 | Due to the occasional crashes upon application start, the application is especially designed to run in the background while working on the CLI from the computer. 30 | 31 | You can use the CLI to start the application: 32 | 33 | .. code-block:: shell 34 | 35 | loader open /ext/apps/Tools/upython.fap 36 | 37 | You can also use the CLI to start the application and run a Python script: 38 | 39 | .. code-block:: shell 40 | 41 | loader open /ext/apps/Tools/upython.fap /ext/scripts/tic_tac_toe.py 42 | 43 | Once the application is up an running, it registers the **py** command in the Flipper's CLI command registry. 44 | You can use this command to access the interactive MicroPython shell or start a script by passing the path as an argument: 45 | 46 | .. code-block:: shell 47 | 48 | py /ext/scripts/tic_tac_toe.py 49 | 50 | Be aware, that the **py** command is only available as long as the application is running on the Flipper. 51 | Furthermore, you can only run one script at the time. 52 | -------------------------------------------------------------------------------- /docs/pages/reference.rst: -------------------------------------------------------------------------------- 1 | Reference 2 | ========= 3 | 4 | This page contains the API documentation of the ``flipperzero`` module and some built-in functions. 5 | The module is also available as a Python package on `PyPI `_. 6 | Install it in your local development environment if you need code completion support inside your IDE. 7 | 8 | Vibration 9 | --------- 10 | 11 | Control the vibration motor of your Flipper. 12 | 13 | .. autofunction:: flipperzero.vibro_set 14 | 15 | .. _reference-light: 16 | 17 | Light 18 | ----- 19 | 20 | Control the RGB LED and display backlight of your Flipper. 21 | 22 | Constants 23 | ~~~~~~~~~ 24 | 25 | .. autodata:: flipperzero.LIGHT_RED 26 | .. autodata:: flipperzero.LIGHT_GREEN 27 | .. autodata:: flipperzero.LIGHT_BLUE 28 | .. autodata:: flipperzero.LIGHT_BACKLIGHT 29 | 30 | Functions 31 | ~~~~~~~~~ 32 | 33 | .. autofunction:: flipperzero.light_set 34 | .. autofunction:: flipperzero.light_blink_start 35 | .. autofunction:: flipperzero.light_blink_set_color 36 | .. autofunction:: flipperzero.light_blink_stop 37 | 38 | .. _reference-speaker: 39 | 40 | Speaker 41 | ------- 42 | 43 | Full control over the built-in speaker module. 44 | 45 | Musical Notes 46 | ~~~~~~~~~~~~~ 47 | 48 | Constant values for all musical notes between C\ :sub:`0` and B\ :sub:`8`. 49 | 50 | .. warning:: 51 | 52 | You won't be able to find these constants in the REPL using autocompletion from version 1.7.0 onwards. 53 | But the constants are still available. You just have to type the full name by hand. 54 | 55 | .. 56 | for octave in range(9): 57 | for name in ['C', 'CS', 'D', 'DS', 'E', 'F', 'FS', 'G', 'GS', 'A', 'AS', 'B']: 58 | print(f'.. autodata:: flipperzero.SPEAKER_NOTE_{name}{octave}') 59 | 60 | .. autodata:: flipperzero.SPEAKER_NOTE_C0 61 | .. autodata:: flipperzero.SPEAKER_NOTE_CS0 62 | .. autodata:: flipperzero.SPEAKER_NOTE_D0 63 | .. autodata:: flipperzero.SPEAKER_NOTE_DS0 64 | .. autodata:: flipperzero.SPEAKER_NOTE_E0 65 | .. autodata:: flipperzero.SPEAKER_NOTE_F0 66 | .. autodata:: flipperzero.SPEAKER_NOTE_FS0 67 | .. autodata:: flipperzero.SPEAKER_NOTE_G0 68 | .. autodata:: flipperzero.SPEAKER_NOTE_GS0 69 | .. autodata:: flipperzero.SPEAKER_NOTE_A0 70 | .. autodata:: flipperzero.SPEAKER_NOTE_AS0 71 | .. autodata:: flipperzero.SPEAKER_NOTE_B0 72 | .. autodata:: flipperzero.SPEAKER_NOTE_C1 73 | .. autodata:: flipperzero.SPEAKER_NOTE_CS1 74 | .. autodata:: flipperzero.SPEAKER_NOTE_D1 75 | .. autodata:: flipperzero.SPEAKER_NOTE_DS1 76 | .. autodata:: flipperzero.SPEAKER_NOTE_E1 77 | .. autodata:: flipperzero.SPEAKER_NOTE_F1 78 | .. autodata:: flipperzero.SPEAKER_NOTE_FS1 79 | .. autodata:: flipperzero.SPEAKER_NOTE_G1 80 | .. autodata:: flipperzero.SPEAKER_NOTE_GS1 81 | .. autodata:: flipperzero.SPEAKER_NOTE_A1 82 | .. autodata:: flipperzero.SPEAKER_NOTE_AS1 83 | .. autodata:: flipperzero.SPEAKER_NOTE_B1 84 | .. autodata:: flipperzero.SPEAKER_NOTE_C2 85 | .. autodata:: flipperzero.SPEAKER_NOTE_CS2 86 | .. autodata:: flipperzero.SPEAKER_NOTE_D2 87 | .. autodata:: flipperzero.SPEAKER_NOTE_DS2 88 | .. autodata:: flipperzero.SPEAKER_NOTE_E2 89 | .. autodata:: flipperzero.SPEAKER_NOTE_F2 90 | .. autodata:: flipperzero.SPEAKER_NOTE_FS2 91 | .. autodata:: flipperzero.SPEAKER_NOTE_G2 92 | .. autodata:: flipperzero.SPEAKER_NOTE_GS2 93 | .. autodata:: flipperzero.SPEAKER_NOTE_A2 94 | .. autodata:: flipperzero.SPEAKER_NOTE_AS2 95 | .. autodata:: flipperzero.SPEAKER_NOTE_B2 96 | .. autodata:: flipperzero.SPEAKER_NOTE_C3 97 | .. autodata:: flipperzero.SPEAKER_NOTE_CS3 98 | .. autodata:: flipperzero.SPEAKER_NOTE_D3 99 | .. autodata:: flipperzero.SPEAKER_NOTE_DS3 100 | .. autodata:: flipperzero.SPEAKER_NOTE_E3 101 | .. autodata:: flipperzero.SPEAKER_NOTE_F3 102 | .. autodata:: flipperzero.SPEAKER_NOTE_FS3 103 | .. autodata:: flipperzero.SPEAKER_NOTE_G3 104 | .. autodata:: flipperzero.SPEAKER_NOTE_GS3 105 | .. autodata:: flipperzero.SPEAKER_NOTE_A3 106 | .. autodata:: flipperzero.SPEAKER_NOTE_AS3 107 | .. autodata:: flipperzero.SPEAKER_NOTE_B3 108 | .. autodata:: flipperzero.SPEAKER_NOTE_C4 109 | .. autodata:: flipperzero.SPEAKER_NOTE_CS4 110 | .. autodata:: flipperzero.SPEAKER_NOTE_D4 111 | .. autodata:: flipperzero.SPEAKER_NOTE_DS4 112 | .. autodata:: flipperzero.SPEAKER_NOTE_E4 113 | .. autodata:: flipperzero.SPEAKER_NOTE_F4 114 | .. autodata:: flipperzero.SPEAKER_NOTE_FS4 115 | .. autodata:: flipperzero.SPEAKER_NOTE_G4 116 | .. autodata:: flipperzero.SPEAKER_NOTE_GS4 117 | .. autodata:: flipperzero.SPEAKER_NOTE_A4 118 | .. autodata:: flipperzero.SPEAKER_NOTE_AS4 119 | .. autodata:: flipperzero.SPEAKER_NOTE_B4 120 | .. autodata:: flipperzero.SPEAKER_NOTE_C5 121 | .. autodata:: flipperzero.SPEAKER_NOTE_CS5 122 | .. autodata:: flipperzero.SPEAKER_NOTE_D5 123 | .. autodata:: flipperzero.SPEAKER_NOTE_DS5 124 | .. autodata:: flipperzero.SPEAKER_NOTE_E5 125 | .. autodata:: flipperzero.SPEAKER_NOTE_F5 126 | .. autodata:: flipperzero.SPEAKER_NOTE_FS5 127 | .. autodata:: flipperzero.SPEAKER_NOTE_G5 128 | .. autodata:: flipperzero.SPEAKER_NOTE_GS5 129 | .. autodata:: flipperzero.SPEAKER_NOTE_A5 130 | .. autodata:: flipperzero.SPEAKER_NOTE_AS5 131 | .. autodata:: flipperzero.SPEAKER_NOTE_B5 132 | .. autodata:: flipperzero.SPEAKER_NOTE_C6 133 | .. autodata:: flipperzero.SPEAKER_NOTE_CS6 134 | .. autodata:: flipperzero.SPEAKER_NOTE_D6 135 | .. autodata:: flipperzero.SPEAKER_NOTE_DS6 136 | .. autodata:: flipperzero.SPEAKER_NOTE_E6 137 | .. autodata:: flipperzero.SPEAKER_NOTE_F6 138 | .. autodata:: flipperzero.SPEAKER_NOTE_FS6 139 | .. autodata:: flipperzero.SPEAKER_NOTE_G6 140 | .. autodata:: flipperzero.SPEAKER_NOTE_GS6 141 | .. autodata:: flipperzero.SPEAKER_NOTE_A6 142 | .. autodata:: flipperzero.SPEAKER_NOTE_AS6 143 | .. autodata:: flipperzero.SPEAKER_NOTE_B6 144 | .. autodata:: flipperzero.SPEAKER_NOTE_C7 145 | .. autodata:: flipperzero.SPEAKER_NOTE_CS7 146 | .. autodata:: flipperzero.SPEAKER_NOTE_D7 147 | .. autodata:: flipperzero.SPEAKER_NOTE_DS7 148 | .. autodata:: flipperzero.SPEAKER_NOTE_E7 149 | .. autodata:: flipperzero.SPEAKER_NOTE_F7 150 | .. autodata:: flipperzero.SPEAKER_NOTE_FS7 151 | .. autodata:: flipperzero.SPEAKER_NOTE_G7 152 | .. autodata:: flipperzero.SPEAKER_NOTE_GS7 153 | .. autodata:: flipperzero.SPEAKER_NOTE_A7 154 | .. autodata:: flipperzero.SPEAKER_NOTE_AS7 155 | .. autodata:: flipperzero.SPEAKER_NOTE_B7 156 | .. autodata:: flipperzero.SPEAKER_NOTE_C8 157 | .. autodata:: flipperzero.SPEAKER_NOTE_CS8 158 | .. autodata:: flipperzero.SPEAKER_NOTE_D8 159 | .. autodata:: flipperzero.SPEAKER_NOTE_DS8 160 | .. autodata:: flipperzero.SPEAKER_NOTE_E8 161 | .. autodata:: flipperzero.SPEAKER_NOTE_F8 162 | .. autodata:: flipperzero.SPEAKER_NOTE_FS8 163 | .. autodata:: flipperzero.SPEAKER_NOTE_G8 164 | .. autodata:: flipperzero.SPEAKER_NOTE_GS8 165 | .. autodata:: flipperzero.SPEAKER_NOTE_A8 166 | .. autodata:: flipperzero.SPEAKER_NOTE_AS8 167 | .. autodata:: flipperzero.SPEAKER_NOTE_B8 168 | 169 | Volume 170 | ~~~~~~ 171 | 172 | .. autodata:: flipperzero.SPEAKER_VOLUME_MIN 173 | .. autodata:: flipperzero.SPEAKER_VOLUME_MAX 174 | 175 | Functions 176 | ~~~~~~~~~ 177 | 178 | .. autofunction:: flipperzero.speaker_start 179 | .. autofunction:: flipperzero.speaker_set_volume 180 | .. autofunction:: flipperzero.speaker_stop 181 | 182 | .. _reference-input: 183 | 184 | Input 185 | ----- 186 | 187 | Make your application interactive with full control over the Flipper's hardware buttons. 188 | 189 | Buttons 190 | ~~~~~~~ 191 | 192 | .. autodata:: flipperzero.INPUT_BUTTON_UP 193 | .. autodata:: flipperzero.INPUT_BUTTON_DOWN 194 | .. autodata:: flipperzero.INPUT_BUTTON_RIGHT 195 | .. autodata:: flipperzero.INPUT_BUTTON_LEFT 196 | .. autodata:: flipperzero.INPUT_BUTTON_OK 197 | .. autodata:: flipperzero.INPUT_BUTTON_BACK 198 | 199 | Events 200 | ~~~~~~ 201 | 202 | .. autodata:: flipperzero.INPUT_TYPE_PRESS 203 | .. autodata:: flipperzero.INPUT_TYPE_RELEASE 204 | .. autodata:: flipperzero.INPUT_TYPE_SHORT 205 | .. autodata:: flipperzero.INPUT_TYPE_LONG 206 | .. autodata:: flipperzero.INPUT_TYPE_REPEAT 207 | 208 | Functions 209 | ~~~~~~~~~ 210 | 211 | .. autodecorator:: flipperzero.on_input 212 | 213 | .. _reference-canvas: 214 | 215 | Canvas 216 | ------ 217 | 218 | Write text and draw dots and shapes on the the display. 219 | 220 | Basics 221 | ~~~~~~ 222 | 223 | .. autofunction:: flipperzero.canvas_update 224 | .. autofunction:: flipperzero.canvas_clear 225 | .. autofunction:: flipperzero.canvas_width 226 | .. autofunction:: flipperzero.canvas_height 227 | 228 | Colors 229 | ~~~~~~ 230 | 231 | .. autodata:: flipperzero.COLOR_BLACK 232 | .. autodata:: flipperzero.COLOR_WHITE 233 | .. autofunction:: flipperzero.canvas_set_color 234 | 235 | Alignment 236 | ~~~~~~~~~ 237 | 238 | .. autodata:: flipperzero.ALIGN_BEGIN 239 | .. autodata:: flipperzero.ALIGN_END 240 | .. autodata:: flipperzero.ALIGN_CENTER 241 | .. autofunction:: flipperzero.canvas_set_text_align 242 | 243 | Text 244 | ~~~~ 245 | 246 | .. autodata:: flipperzero.FONT_PRIMARY 247 | .. autodata:: flipperzero.FONT_SECONDARY 248 | .. autofunction:: flipperzero.canvas_set_font 249 | .. autofunction:: flipperzero.canvas_set_text 250 | 251 | Shapes 252 | ~~~~~~ 253 | 254 | .. autofunction:: flipperzero.canvas_draw_dot 255 | .. autofunction:: flipperzero.canvas_draw_box 256 | .. autofunction:: flipperzero.canvas_draw_frame 257 | .. autofunction:: flipperzero.canvas_draw_line 258 | .. autofunction:: flipperzero.canvas_draw_circle 259 | .. autofunction:: flipperzero.canvas_draw_disc 260 | 261 | Dialog 262 | ------ 263 | 264 | Display message dialogs on the display for user infos and confirm actions. 265 | 266 | .. autofunction:: flipperzero.dialog_message_set_header 267 | .. autofunction:: flipperzero.dialog_message_set_text 268 | .. autofunction:: flipperzero.dialog_message_set_button 269 | .. autofunction:: flipperzero.dialog_message_show 270 | 271 | .. _reference-gpio: 272 | 273 | GPIO 274 | ---- 275 | 276 | Access to the GPIO pins of your Flipper. 277 | 278 | Pins 279 | ~~~~ 280 | 281 | .. autodata:: flipperzero.GPIO_PIN_PC0 282 | .. autodata:: flipperzero.GPIO_PIN_PC1 283 | .. autodata:: flipperzero.GPIO_PIN_PC3 284 | .. autodata:: flipperzero.GPIO_PIN_PB2 285 | .. autodata:: flipperzero.GPIO_PIN_PB3 286 | .. autodata:: flipperzero.GPIO_PIN_PA4 287 | .. autodata:: flipperzero.GPIO_PIN_PA6 288 | .. autodata:: flipperzero.GPIO_PIN_PA7 289 | 290 | Modes 291 | ~~~~~ 292 | 293 | .. autodata:: flipperzero.GPIO_MODE_INPUT 294 | .. autodata:: flipperzero.GPIO_MODE_OUTPUT_PUSH_PULL 295 | .. autodata:: flipperzero.GPIO_MODE_OUTPUT_OPEN_DRAIN 296 | .. autodata:: flipperzero.GPIO_MODE_ANALOG 297 | .. autodata:: flipperzero.GPIO_MODE_INTERRUPT_RISE 298 | .. autodata:: flipperzero.GPIO_MODE_INTERRUPT_FALL 299 | 300 | Pull 301 | ~~~~ 302 | 303 | .. autodata:: flipperzero.GPIO_PULL_NO 304 | .. autodata:: flipperzero.GPIO_PULL_UP 305 | .. autodata:: flipperzero.GPIO_PULL_DOWN 306 | 307 | Speed 308 | ~~~~~ 309 | 310 | .. autodata:: flipperzero.GPIO_SPEED_LOW 311 | .. autodata:: flipperzero.GPIO_SPEED_MEDIUM 312 | .. autodata:: flipperzero.GPIO_SPEED_HIGH 313 | .. autodata:: flipperzero.GPIO_SPEED_VERY_HIGH 314 | 315 | Functions 316 | ~~~~~~~~~ 317 | 318 | .. autofunction:: flipperzero.gpio_init_pin 319 | .. autofunction:: flipperzero.gpio_deinit_pin 320 | .. autofunction:: flipperzero.gpio_set_pin 321 | .. autofunction:: flipperzero.gpio_get_pin 322 | .. autodecorator:: flipperzero.on_gpio 323 | 324 | .. _reference-adc: 325 | 326 | ADC 327 | --- 328 | 329 | Read analog values from selected GPIO pins: 330 | 331 | * :const:`flipperzero.GPIO_PIN_PC0` 332 | * :const:`flipperzero.GPIO_PIN_PC1` 333 | * :const:`flipperzero.GPIO_PIN_PC3` 334 | * :const:`flipperzero.GPIO_PIN_PA4` 335 | * :const:`flipperzero.GPIO_PIN_PA6` 336 | * :const:`flipperzero.GPIO_PIN_PA7` 337 | 338 | The corresponding pin must be initialized in the analog mode: 339 | 340 | .. code-block:: 341 | 342 | import flipperzero as f0 343 | 344 | f0.gpio_init_pin(f0.GPIO_PIN_PC0, f0.GPIO_MODE_ANALOG) 345 | 346 | This configures the pin as ADC input with the following settings: 347 | 348 | * Reference voltage is set to 2.048 V. 349 | * Clock speed is at 64 MHz in synchronous mode. 350 | * Oversample rate is set to 64. 351 | 352 | `This default configuration is best for relatively high impedance circuits with slowly or or not changing signals.` 353 | 354 | Functions 355 | ~~~~~~~~~ 356 | 357 | .. autofunction:: flipperzero.adc_read_pin_value 358 | .. autofunction:: flipperzero.adc_read_pin_voltage 359 | 360 | .. _reference-pwm: 361 | 362 | PWM 363 | --- 364 | 365 | Output a PWM signal on selected GPIO pins: 366 | 367 | * :const:`flipperzero.GPIO_PIN_PA4` 368 | * :const:`flipperzero.GPIO_PIN_PA7` 369 | 370 | Functions 371 | ~~~~~~~~~ 372 | 373 | .. autofunction:: flipperzero.pwm_start 374 | .. autofunction:: flipperzero.pwm_stop 375 | .. autofunction:: flipperzero.pwm_is_running 376 | 377 | .. _reference-infrared: 378 | 379 | Infrared 380 | -------- 381 | 382 | Send and receive infrared signals. 383 | 384 | Signal Format 385 | ~~~~~~~~~~~~~ 386 | 387 | The format to represent infrared signals uses a simple list of integers. 388 | Each value represents the duration between two signal edges in microseconds. 389 | Since this is a digital signal, there are only two levels: `high` and `low`. 390 | The timing list always starts with a `high` level. 391 | 392 | .. literalinclude:: ./assets/pwm_signal.txt 393 | :language: text 394 | 395 | .. hint:: 396 | 397 | This is equal to the raw signal format of the `IR file `_ specification. 398 | 399 | Functions 400 | ~~~~~~~~~ 401 | 402 | .. autofunction:: flipperzero.infrared_receive 403 | .. autofunction:: flipperzero.infrared_transmit 404 | .. autofunction:: flipperzero.infrared_is_busy 405 | 406 | UART 407 | ---- 408 | 409 | Connect to UART enabled devices. 410 | 411 | Modes 412 | ~~~~~ 413 | 414 | .. autodata:: flipperzero.UART_MODE_LPUART 415 | .. autodata:: flipperzero.UART_MODE_USART 416 | 417 | Functions 418 | ~~~~~~~~~ 419 | 420 | .. autofunction:: flipperzero.uart_open 421 | 422 | Classes 423 | ~~~~~~~ 424 | 425 | .. autoclass:: flipperzero.UART 426 | :members: read, readline, readlines, write, flush, close, __enter__, __exit__, __del__ 427 | 428 | Logging 429 | ------- 430 | 431 | Log messages to the Flipper's own logging backend. 432 | Check out the `Flipper Zero docs `_ on how to reveal them in the CLI. 433 | Be aware, that you can't change Flipper's global log level from within your script. 434 | Change the `corresponding settings `_ instead or use the **log** command in the CLI with the desired log level as the first argument. 435 | 436 | Levels 437 | ~~~~~~ 438 | 439 | .. autodata:: logging.TRACE 440 | .. autodata:: logging.DEBUG 441 | .. autodata:: logging.INFO 442 | .. autodata:: logging.WARN 443 | .. autodata:: logging.ERROR 444 | .. autodata:: logging.NONE 445 | .. autodata:: logging.level 446 | 447 | Functions 448 | ~~~~~~~~~ 449 | 450 | .. autofunction:: logging.setLevel 451 | .. autofunction:: logging.getEffectiveLevel 452 | .. autofunction:: logging.trace 453 | .. autofunction:: logging.debug 454 | .. autofunction:: logging.info 455 | .. autofunction:: logging.warn 456 | .. autofunction:: logging.error 457 | .. autofunction:: logging.log 458 | 459 | I/O 460 | --- 461 | 462 | Read and write SD card files. 463 | 464 | Constants 465 | ~~~~~~~~~ 466 | 467 | .. autodata:: io.SEEK_SET 468 | .. autodata:: io.SEEK_CUR 469 | .. autodata:: io.SEEK_END 470 | 471 | Functions 472 | ~~~~~~~~~ 473 | 474 | .. autofunction:: io.open 475 | 476 | Classes 477 | ~~~~~~~ 478 | 479 | .. autoclass:: io.BinaryFileIO 480 | :members: name, read, readline, readlines, readable, writable, write, flush, seek, tell, close, __enter__, __exit__, __del__ 481 | 482 | .. autoclass:: io.TextFileIO 483 | :members: name, read, readline, readlines, readable, writable, write, flush, seek, tell, close, __enter__, __exit__, __del__ 484 | 485 | Random 486 | ------ 487 | 488 | Generate pseudo-random number for various use cases. 489 | 490 | Functions 491 | ~~~~~~~~~ 492 | 493 | .. autofunction:: random.random 494 | .. autofunction:: random.uniform 495 | .. autofunction:: random.randrange 496 | .. autofunction:: random.getrandbits 497 | .. autofunction:: random.randint 498 | .. autofunction:: random.choice 499 | .. autofunction:: random.seed 500 | 501 | Built-In 502 | -------- 503 | 504 | The functions in this section are `not` part of the ``flipperzero`` module. 505 | They're members of the global namespace instead. 506 | 507 | .. py:function:: print(*objects, sep=' ', end='\n') -> None 508 | 509 | The standard Python `print `_ function. 510 | Where the output of this function will be redirected depends on how the script is invoked: 511 | 512 | * When invoked from the UI, the output will be sent to the Flipper's log buffer. 513 | Check out the `Flipper Zero docs `_ on how to view them in the CLI interface. 514 | * In the REPL, the output will be sent to the standard output buffer. 515 | * When invoked by the **py** command, the output will be sent to the standard output buffer. 516 | 517 | :param objects: The objects to print (mostly a single string). 518 | :param sep: The separator to use between the objects. 519 | :param end: The line terminator character to use. 520 | 521 | .. versionadded:: 1.0.0 522 | .. versionchanged:: 1.5.0 523 | 524 | Output redirection, based on script invocation. 525 | -------------------------------------------------------------------------------- /docs/pages/roadmap.md: -------------------------------------------------------------------------------- 1 | # Roadmap 2 | 3 | This is the roadmap for the application. 4 | Here you can see what to expect from future releases. 5 | 6 | ## Next Release 7 | 8 | * SPI 9 | * I2C 10 | * Maybe USB HID 11 | 12 | _The next release might takes the form of a fork of the official firmware._ 13 | _Check out [this issue](https://github.com/ofabel/mp-flipper/issues/4) for details._ 14 | 15 | ## Planned 16 | 17 | * I2C 18 | * NFC 19 | * USB HID 20 | * Subghz 21 | * Bluetooth 22 | * ... 23 | 24 | Feel free to [file an issue](https://www.github.com/ofabel/mp-flipper/issues), if you miss any particular feature. 25 | -------------------------------------------------------------------------------- /docs/tic-tac-toe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ofabel/mp-flipper/602080eca907cad035f570541384137d786ad52a/docs/tic-tac-toe.png -------------------------------------------------------------------------------- /docs/welcome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ofabel/mp-flipper/602080eca907cad035f570541384137d786ad52a/docs/welcome.png -------------------------------------------------------------------------------- /examples/dict_test.py: -------------------------------------------------------------------------------- 1 | d = dict() 2 | 3 | for i in range(1,100): 4 | d[str(i)] = i 5 | 6 | for k,v in d.items(): 7 | val = '{0} = {1}'.format(k, v) 8 | 9 | print(val) 10 | -------------------------------------------------------------------------------- /examples/flipperzero_adc_test.py: -------------------------------------------------------------------------------- 1 | import flipperzero as f0 2 | import time 3 | 4 | f0.gpio_init_pin(f0.GPIO_PIN_PC1, f0.GPIO_MODE_ANALOG) 5 | 6 | for _ in range(1,1000): 7 | raw_value = f0.adc_read_pin_value(f0.GPIO_PIN_PC1) 8 | raw_voltage = f0.adc_read_pin_voltage(f0.GPIO_PIN_PC1) 9 | 10 | value = '{value} #'.format(value=raw_value) 11 | voltage = '{value} mV'.format(value=raw_voltage) 12 | 13 | f0.canvas_clear() 14 | 15 | f0.canvas_set_text(10, 32, value) 16 | f0.canvas_set_text(70, 32, voltage) 17 | 18 | f0.canvas_update() 19 | 20 | time.sleep_ms(10) 21 | -------------------------------------------------------------------------------- /examples/flipperzero_canvas_test.py: -------------------------------------------------------------------------------- 1 | import time 2 | import flipperzero 3 | 4 | color = False 5 | 6 | def draw_action(): 7 | print('on draw') 8 | 9 | global color 10 | 11 | for x in range(0, 128): 12 | color = not color 13 | 14 | for y in range(0, 64): 15 | flipperzero.canvas_set_color(flipperzero.CANVAS_BLACK if color else flipperzero.CANVAS_WHITE) 16 | flipperzero.canvas_draw_dot(x, y) 17 | 18 | color = not color 19 | 20 | color = not color 21 | 22 | flipperzero.canvas_set_text(64, 32, "Test") 23 | 24 | flipperzero.canvas_update() 25 | 26 | print('start') 27 | 28 | draw_action() 29 | 30 | for _ in range(1, 5): 31 | time.sleep(1) 32 | 33 | draw_action() 34 | -------------------------------------------------------------------------------- /examples/flipperzero_dialog_message_test.py: -------------------------------------------------------------------------------- 1 | import flipperzero as f0 2 | 3 | f0.dialog_message_set_header('Important',64, 12) 4 | f0.dialog_message_set_text('Shutdown?', 64, 24) 5 | f0.dialog_message_set_button('Yes', f0.INPUT_BUTTON_LEFT) 6 | f0.dialog_message_set_button('No', f0.INPUT_BUTTON_RIGHT) 7 | 8 | while f0.dialog_message_show() is not f0.INPUT_BUTTON_LEFT: 9 | pass 10 | -------------------------------------------------------------------------------- /examples/flipperzero_draw_on_input_test.py: -------------------------------------------------------------------------------- 1 | import time 2 | import flipperzero 3 | 4 | @flipperzero.on_input 5 | def on_input(button, type): 6 | flipperzero.canvas_clear() 7 | flipperzero.canvas_set_color(flipperzero.CANVAS_BLACK) 8 | flipperzero.canvas_set_text(64, 32, '{button} - {type}'.format(button=button, type=type)) 9 | flipperzero.canvas_update() 10 | 11 | for _ in range(1,1000): 12 | time.sleep_ms(10) 13 | -------------------------------------------------------------------------------- /examples/flipperzero_gpio_input_test.py: -------------------------------------------------------------------------------- 1 | import flipperzero as f0 2 | import time 3 | 4 | f0.gpio_init_pin(f0.GPIO_PIN_PA7, f0.GPIO_MODE_OUTPUT_PUSH_PULL) 5 | f0.gpio_init_pin(f0.GPIO_PIN_PC1, f0.GPIO_MODE_INPUT, f0.GPIO_PULL_UP, f0.GPIO_SPEED_HIGH) 6 | 7 | for _ in range(0,15): 8 | state = f0.gpio_get_pin(f0.GPIO_PIN_PC1) 9 | 10 | f0.gpio_set_pin(f0.GPIO_PIN_PA7, state) 11 | 12 | time.sleep(1) 13 | -------------------------------------------------------------------------------- /examples/flipperzero_gpio_interrupt_test.py: -------------------------------------------------------------------------------- 1 | import flipperzero as f0 2 | import time 3 | 4 | # init pins 5 | f0.gpio_init_pin(f0.GPIO_PIN_PA7, f0.GPIO_MODE_OUTPUT_PUSH_PULL) 6 | f0.gpio_init_pin(f0.GPIO_PIN_PC0, f0.GPIO_MODE_INTERRUPT_RISE, f0.GPIO_PULL_UP, f0.GPIO_SPEED_VERY_HIGH) 7 | f0.gpio_init_pin(f0.GPIO_PIN_PC1, f0.GPIO_MODE_INTERRUPT_RISE, f0.GPIO_PULL_UP, f0.GPIO_SPEED_VERY_HIGH) 8 | 9 | @f0.on_gpio 10 | def on_gpio(pin): 11 | if pin == f0.GPIO_PIN_PC0: 12 | f0.gpio_set_pin(f0.GPIO_PIN_PA7, True) 13 | if pin == f0.GPIO_PIN_PC1: 14 | f0.gpio_set_pin(f0.GPIO_PIN_PA7, False) 15 | 16 | for _ in range(1, 1500): 17 | time.sleep_ms(10) 18 | 19 | # reset pins 20 | f0.gpio_init_pin(f0.GPIO_PIN_PA7, f0.GPIO_MODE_ANALOG) 21 | f0.gpio_init_pin(f0.GPIO_PIN_PC0, f0.GPIO_MODE_ANALOG) 22 | f0.gpio_init_pin(f0.GPIO_PIN_PC1, f0.GPIO_MODE_ANALOG) 23 | -------------------------------------------------------------------------------- /examples/flipperzero_gpio_test.py: -------------------------------------------------------------------------------- 1 | import flipperzero as f0 2 | import time 3 | 4 | f0.gpio_init_pin(f0.GPIO_PIN_PA7, f0.GPIO_MODE_OUTPUT_PUSH_PULL) 5 | 6 | f0.gpio_set_pin(f0.GPIO_PIN_PA7, True) 7 | time.sleep(1) 8 | f0.gpio_set_pin(f0.GPIO_PIN_PA7, False) 9 | time.sleep(1) 10 | f0.gpio_set_pin(f0.GPIO_PIN_PA7, True) 11 | time.sleep(1) 12 | f0.gpio_set_pin(f0.GPIO_PIN_PA7, False) 13 | -------------------------------------------------------------------------------- /examples/flipperzero_infrared_test.py: -------------------------------------------------------------------------------- 1 | import flipperzero as f0 2 | import time 3 | 4 | # receive IR signal 5 | signal = f0.infrared_receive() 6 | 7 | time.sleep(3) 8 | 9 | # transmit received signal 10 | f0.infrared_transmit(signal) 11 | -------------------------------------------------------------------------------- /examples/flipperzero_input_test.py: -------------------------------------------------------------------------------- 1 | import time 2 | import flipperzero 3 | 4 | @flipperzero.on_input 5 | def on_input(button, type): 6 | print('{button} - {type}'.format(button=button, type=type)) 7 | 8 | for _ in range(1,1000): 9 | time.sleep_ms(10) 10 | -------------------------------------------------------------------------------- /examples/flipperzero_light_test.py: -------------------------------------------------------------------------------- 1 | import time 2 | import flipperzero 3 | 4 | is_red = True 5 | 6 | for i in range(0, 25, 1): 7 | brightness = i * 10 8 | is_red = not is_red 9 | 10 | flipperzero.light_set(flipperzero.LIGHT_RED, brightness if is_red else 0) 11 | flipperzero.light_set(flipperzero.LIGHT_GREEN, brightness if not is_red else 0) 12 | flipperzero.light_set(flipperzero.LIGHT_BLUE, 0) 13 | flipperzero.light_set(flipperzero.LIGHT_BACKLIGHT, brightness) 14 | 15 | time.sleep_ms(200) 16 | 17 | flipperzero.light_set(flipperzero.LIGHT_RED, 0) 18 | flipperzero.light_set(flipperzero.LIGHT_GREEN, 0) 19 | flipperzero.light_set(flipperzero.LIGHT_BLUE, 0) 20 | flipperzero.light_set(flipperzero.LIGHT_BACKLIGHT, 0) 21 | 22 | time.sleep_ms(500) 23 | 24 | flipperzero.light_blink_start(flipperzero.LIGHT_RED, 200, 100, 200) 25 | 26 | time.sleep(1) 27 | 28 | flipperzero.light_blink_set_color(flipperzero.LIGHT_BLUE) 29 | 30 | time.sleep(1) 31 | 32 | flipperzero.light_blink_stop() 33 | -------------------------------------------------------------------------------- /examples/flipperzero_pwm_test.py: -------------------------------------------------------------------------------- 1 | import flipperzero as f0 2 | import time 3 | 4 | f0.pwm_start(f0.GPIO_PIN_PA7, 4, 50) 5 | time.sleep(3) 6 | 7 | f0.pwm_start(f0.GPIO_PIN_PA7, 1, 25) 8 | time.sleep(3) 9 | -------------------------------------------------------------------------------- /examples/flipperzero_speaker_note_test.py: -------------------------------------------------------------------------------- 1 | import time 2 | import flipperzero as f0 3 | 4 | def play_frequency(frequency: float): 5 | volume = 0.8 6 | 7 | f0.speaker_start(frequency, volume) 8 | 9 | for _ in range(0, 150): 10 | volume *= 0.9945679 11 | 12 | f0.speaker_set_volume(volume) 13 | 14 | time.sleep_ms(1) 15 | 16 | f0.speaker_stop() 17 | 18 | play_frequency(f0.SPEAKER_NOTE_C5) 19 | play_frequency(f0.SPEAKER_NOTE_D5) 20 | play_frequency(f0.SPEAKER_NOTE_E5) 21 | play_frequency(f0.SPEAKER_NOTE_F5) 22 | play_frequency(f0.SPEAKER_NOTE_G5) 23 | play_frequency(f0.SPEAKER_NOTE_A5) 24 | play_frequency(f0.SPEAKER_NOTE_B5) 25 | play_frequency(f0.SPEAKER_NOTE_C6) 26 | -------------------------------------------------------------------------------- /examples/flipperzero_speaker_test.py: -------------------------------------------------------------------------------- 1 | import time 2 | import flipperzero 3 | 4 | def play_frequency(frequency: float): 5 | volume = 0.8 6 | 7 | flipperzero.speaker_start(frequency, volume) 8 | 9 | for _ in range(0, 150): 10 | volume *= 0.9945679 11 | 12 | flipperzero.speaker_set_volume(volume) 13 | 14 | time.sleep_ms(1) 15 | 16 | flipperzero.speaker_stop() 17 | 18 | play_frequency(100.0) 19 | play_frequency(200.0) 20 | play_frequency(300.0) 21 | play_frequency(500.0) 22 | play_frequency(800.0) 23 | play_frequency(1300.0) 24 | play_frequency(2100.0) 25 | play_frequency(3400.0) 26 | -------------------------------------------------------------------------------- /examples/flipperzero_vibro_test.py: -------------------------------------------------------------------------------- 1 | import time 2 | import flipperzero 3 | 4 | flipperzero.vibro_set(True) 5 | 6 | time.sleep_ms(125) 7 | 8 | flipperzero.vibro_set(False) 9 | -------------------------------------------------------------------------------- /examples/gc_test.py: -------------------------------------------------------------------------------- 1 | import gc 2 | 3 | gc.collect() 4 | -------------------------------------------------------------------------------- /examples/greeter_test.py: -------------------------------------------------------------------------------- 1 | class Greeter: 2 | def __init__(self, name: str): 3 | self._name = name 4 | 5 | def __str__(self): 6 | return 'hello {name}!'.format(name=self._name) 7 | 8 | def __repr__(self): 9 | return self.__str__() 10 | 11 | world_greeter = Greeter('world') 12 | 13 | print(world_greeter) 14 | -------------------------------------------------------------------------------- /examples/hello_test.py: -------------------------------------------------------------------------------- 1 | print("hello world!") 2 | -------------------------------------------------------------------------------- /examples/import_error_test.py: -------------------------------------------------------------------------------- 1 | import unknown_module -------------------------------------------------------------------------------- /examples/import_test.py: -------------------------------------------------------------------------------- 1 | from greeter_test import Greeter 2 | 3 | buddy = Greeter('buddy') 4 | 5 | print(buddy) 6 | -------------------------------------------------------------------------------- /examples/infrared_signal_viewer.py: -------------------------------------------------------------------------------- 1 | import flipperzero as f0 2 | import time 3 | 4 | terminate = False 5 | index = 0 6 | signal = [] 7 | 8 | def draw_signal(): 9 | global index 10 | global signal 11 | 12 | f0.canvas_clear() 13 | 14 | level = False if index % 2 else True 15 | x = 0 16 | y_low = 32 17 | y_high = 40 18 | y_level = y_low 19 | 20 | for i in range(100): 21 | i += index 22 | 23 | if i > len(signal): 24 | break 25 | 26 | duration = int(signal[i] / 100) 27 | 28 | if level: 29 | f0.canvas_draw_line(x, y_low, x, y_high) 30 | y_level = y_high 31 | else: 32 | f0.canvas_draw_line(x, y_high, x, y_low) 33 | y_level = y_low 34 | 35 | f0.canvas_draw_line(x, y_level, x + duration, y_level) 36 | 37 | x += duration 38 | 39 | level = not level 40 | 41 | if x > f0.canvas_width(): 42 | break 43 | 44 | f0.canvas_update() 45 | 46 | @f0.on_input 47 | def on_input(button, type): 48 | global terminate 49 | global index 50 | global signal 51 | 52 | # terminate upon back button press 53 | if button == f0.INPUT_BUTTON_BACK and type == f0.INPUT_TYPE_SHORT: 54 | terminate = True 55 | 56 | return 57 | 58 | # transmit signal upon ok button 59 | if button == f0.INPUT_BUTTON_OK and type == f0.INPUT_TYPE_SHORT and len(signal) > 0: 60 | f0.infrared_transmit(signal) 61 | 62 | return 63 | 64 | # scroll left upon button left 65 | if button == f0.INPUT_BUTTON_LEFT and type == f0.INPUT_TYPE_SHORT: 66 | index -= 1 if index > 0 else 0 67 | 68 | draw_signal() 69 | 70 | return 71 | 72 | # scroll right upon button left 73 | if button == f0.INPUT_BUTTON_RIGHT and type == f0.INPUT_TYPE_SHORT: 74 | index += 1 75 | index %= len(signal) 76 | 77 | draw_signal() 78 | 79 | return 80 | 81 | f0.canvas_set_text(10, 32, 'Waiting for IR signal ...') 82 | f0.canvas_update() 83 | 84 | # receive signal 85 | signal = f0.infrared_receive() 86 | 87 | if len(signal): 88 | draw_signal() 89 | else: 90 | terminate = True 91 | 92 | # wait upon termination 93 | while not terminate: 94 | time.sleep_ms(1) 95 | -------------------------------------------------------------------------------- /examples/logger.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | logging.setLevel(logging.TRACE) 4 | 5 | logging.trace('trace') 6 | logging.debug('debug %d %s',123,'message') 7 | logging.info('info %d %s',123,'message') 8 | logging.warn('warn %d %s',123,'message') 9 | logging.error('error %d %s',123,'message') 10 | 11 | logging.log(logging.TRACE, "level: %d", logging.TRACE) 12 | logging.log(logging.DEBUG, "level: %d", logging.DEBUG) 13 | logging.log(logging.INFO, "level: %d", logging.INFO) 14 | logging.log(logging.WARN, "level: %d", logging.WARN) 15 | logging.log(logging.ERROR, "level: %d", logging.ERROR) 16 | -------------------------------------------------------------------------------- /examples/open.py: -------------------------------------------------------------------------------- 1 | fp = open('/ext/spam.txt', 'w') 2 | 3 | fp.write('Some spam') 4 | 5 | fp.close() -------------------------------------------------------------------------------- /examples/overflow_test.py: -------------------------------------------------------------------------------- 1 | data = list(range(0,9999)) 2 | -------------------------------------------------------------------------------- /examples/raise_test.py: -------------------------------------------------------------------------------- 1 | raise Exception('something went wrong') -------------------------------------------------------------------------------- /examples/rand_test.py: -------------------------------------------------------------------------------- 1 | from random import randint, seed 2 | 3 | seed(None) 4 | 5 | for limit in range(2,100): 6 | value = randint(2, limit) 7 | print(value) 8 | -------------------------------------------------------------------------------- /examples/sleep_test.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | time.sleep(5) 4 | -------------------------------------------------------------------------------- /examples/tic_tac_toe.py: -------------------------------------------------------------------------------- 1 | import time 2 | import flipperzero as f0 3 | 4 | def init_grid(): 5 | return [ 6 | [' ', ' ', ' '], 7 | [' ', ' ', ' '], 8 | [' ', ' ', ' '], 9 | ] 10 | 11 | m_exit = False 12 | 13 | m_grid = init_grid() 14 | 15 | m_x = 1 16 | m_y = 1 17 | 18 | m_is_cross = True 19 | 20 | @f0.on_input 21 | def input_handler(button, type): 22 | global m_exit 23 | global m_grid 24 | global m_x 25 | global m_y 26 | global m_is_cross 27 | 28 | if button == f0.INPUT_BUTTON_BACK and type == f0.INPUT_TYPE_LONG: 29 | m_exit = True 30 | elif button == f0.INPUT_BUTTON_BACK and type == f0.INPUT_TYPE_SHORT: 31 | m_grid = init_grid() 32 | elif button == f0.INPUT_BUTTON_LEFT and type == f0.INPUT_TYPE_SHORT: 33 | m_x = (m_x - 1) % 3 34 | elif button == f0.INPUT_BUTTON_RIGHT and type == f0.INPUT_TYPE_SHORT: 35 | m_x = (m_x + 1) % 3 36 | elif button == f0.INPUT_BUTTON_UP and type == f0.INPUT_TYPE_SHORT: 37 | m_y = (m_y - 1) % 3 38 | elif button == f0.INPUT_BUTTON_DOWN and type == f0.INPUT_TYPE_SHORT: 39 | m_y = (m_y + 1) % 3 40 | elif button == f0.INPUT_BUTTON_OK and type == f0.INPUT_TYPE_SHORT: 41 | m_grid[m_x][m_y] = 'X' if m_is_cross else 'O' 42 | m_is_cross = not m_is_cross 43 | 44 | def draw_grid(): 45 | global m_grid 46 | global m_x 47 | global m_y 48 | 49 | f0.canvas_clear() 50 | f0.canvas_draw_frame(2, 2, 60, 60) 51 | 52 | f0.canvas_draw_line(22, 2, 22, 62) 53 | f0.canvas_draw_line(42, 2, 42, 62) 54 | 55 | f0.canvas_draw_line(2, 22, 62, 22) 56 | f0.canvas_draw_line(2, 42, 62, 42) 57 | 58 | px = m_x * 20 + 4 59 | py = m_y * 20 + 4 60 | 61 | f0.canvas_draw_frame(px, py, 16, 16) 62 | 63 | f0.canvas_set_text_align(f0.ALIGN_CENTER, f0.ALIGN_CENTER) 64 | 65 | for x in range(0, 3): 66 | for y in range(0, 3): 67 | px = x * 20 + 10 + 2 68 | py = y * 20 + 10 + 2 69 | f0.canvas_set_text(px, py, m_grid[x][y]) 70 | 71 | f0.canvas_update() 72 | 73 | while not m_exit: 74 | draw_grid() 75 | time.sleep_ms(25) 76 | -------------------------------------------------------------------------------- /examples/try_except_test.py: -------------------------------------------------------------------------------- 1 | try: 2 | raise Exception('something went wrong') 3 | except Exception as e: 4 | print(e) -------------------------------------------------------------------------------- /examples/uart.py: -------------------------------------------------------------------------------- 1 | import time 2 | import flipperzero as f0 3 | 4 | def read_from_uart(): 5 | with f0.uart_open(f0.UART_MODE_USART, 115200) as uart: 6 | for _ in range(1000): 7 | raw_data = uart.read() 8 | 9 | if len(raw_data) > 0: 10 | data = raw_data.decode() 11 | 12 | print(data) 13 | 14 | time.sleep_ms(10) 15 | 16 | read_from_uart() 17 | -------------------------------------------------------------------------------- /flipper.json: -------------------------------------------------------------------------------- 1 | { 2 | "source": "examples", 3 | "target": "/ext/examples", 4 | "orphans": "ignore", 5 | "include": [ 6 | "*.py" 7 | ], 8 | "exclude": [ 9 | "**.git**" 10 | ], 11 | "run": [ 12 | "loader open /ext/apps/Tools/upython.fap" 13 | ] 14 | } -------------------------------------------------------------------------------- /flipperzero/README.md: -------------------------------------------------------------------------------- 1 | # MicroPython on Flipper Zero 2 | 3 | This package contains the type definitions for the `flipperzero` [MicroPython](https://micropython.org/) module. 4 | Check out the [website](https://ofabel.github.io/mp-flipper/) for details. 5 | 6 | Get the interpreter application on [Flipper Lab](https://lab.flipper.net/apps/upython) to write and run your own Python files. 7 | -------------------------------------------------------------------------------- /flipperzero/_adc.py: -------------------------------------------------------------------------------- 1 | def adc_read_pin_value(pin: int) -> int: 2 | ''' 3 | Read the raw value from the ADC channel. 4 | 5 | :param pin: The pin to read (e.g. :const:`GPIO_PIN_PC1`). 6 | :returns: The raw value between 0 and 4095. 7 | 8 | .. versionadded:: 1.3.0 9 | 10 | .. hint:: 11 | 12 | Don't forget to initialize the pin first. 13 | ''' 14 | pass 15 | 16 | def adc_read_pin_voltage(pin: int) -> float: 17 | ''' 18 | Read the voltage from the ADC channel. 19 | 20 | :param pin: The pin to read (e.g. :const:`GPIO_PIN_PC1`). 21 | :returns: The voltage between 0 - 2.048 V with a precision of ~0.1%. 22 | 23 | .. versionadded:: 1.3.0 24 | 25 | .. hint:: 26 | 27 | Don't forget to initialize the pin first. 28 | ''' 29 | pass 30 | -------------------------------------------------------------------------------- /flipperzero/_canvas.py: -------------------------------------------------------------------------------- 1 | def canvas_update() -> None: 2 | ''' 3 | Updates the display buffer with your drawings from the canvas. 4 | 5 | .. versionadded:: 1.0.0 6 | 7 | .. note:: 8 | 9 | Your drawings will only appear on the display after this function call. 10 | ''' 11 | pass 12 | 13 | def canvas_clear() -> None: 14 | ''' 15 | Clear the whole canvas. This does not affect the current display buffer. 16 | You need to call :func:`canvas_update` to reveal your changes. 17 | 18 | .. versionadded:: 1.0.0 19 | ''' 20 | pass 21 | 22 | def canvas_width() -> int: 23 | ''' 24 | Get the canvas width in pixels. 25 | 26 | .. versionadded:: 1.0.0 27 | 28 | :returns: The canvas width. 29 | ''' 30 | pass 31 | 32 | def canvas_height() -> int: 33 | ''' 34 | Get the canvas height in pixels. 35 | 36 | .. versionadded:: 1.0.0 37 | 38 | :returns: The canvas height. 39 | ''' 40 | pass 41 | 42 | COLOR_BLACK: int 43 | ''' 44 | Constant value for the color `black`. 45 | 46 | .. versionadded:: 1.0.0 47 | ''' 48 | 49 | COLOR_WHITE: int 50 | ''' 51 | Constant value for the color `white`. 52 | 53 | .. versionadded:: 1.0.0 54 | ''' 55 | 56 | def canvas_set_color(color: int) -> None: 57 | ''' 58 | Set the color to use when drawing or writing on the canvas. 59 | 60 | .. versionadded:: 1.0.0 61 | 62 | :param color: The color to use. 63 | ''' 64 | pass 65 | 66 | ALIGN_BEGIN: int 67 | ''' 68 | Align element at `begin` (horizontal or vertical, depends on the context). 69 | 70 | .. versionadded:: 1.0.0 71 | ''' 72 | 73 | ALIGN_END: int 74 | ''' 75 | Align element at `end` (horizontal or vertical, depends on the context). 76 | 77 | .. versionadded:: 1.0.0 78 | ''' 79 | 80 | ALIGN_CENTER: int 81 | ''' 82 | Align element at `center` (horizontal or vertical, depends on the context). 83 | 84 | .. versionadded:: 1.0.0 85 | ''' 86 | 87 | def canvas_set_text_align(x: int, y: int) -> None: 88 | ''' 89 | Define how the text should be aligned in relation to the ``x`` and ``y`` coordinates 90 | when writing on the canvas, using the :func:`canvas_set_text` function. 91 | 92 | :param x: The horizontal alignment. 93 | :param y: The vertical alignment. 94 | 95 | .. versionadded:: 1.0.0 96 | ''' 97 | pass 98 | 99 | FONT_PRIMARY: int 100 | ''' 101 | Constant value for the primary font. 102 | 103 | .. versionadded:: 1.0.0 104 | ''' 105 | 106 | FONT_SECONDARY: int 107 | ''' 108 | Constant value for the secondary font. 109 | 110 | .. versionadded:: 1.0.0 111 | ''' 112 | 113 | def canvas_set_font(font: int) -> None: 114 | ''' 115 | Change the font to use when writing on the canvas using the :func:`canvas_set_text` function. 116 | 117 | :param font: The font to use. 118 | 119 | .. versionadded:: 1.0.0 120 | ''' 121 | pass 122 | 123 | def canvas_set_text(x: int, y: int, text: str) -> None: 124 | ''' 125 | Write text on the canvas at the position of ``x`` and ``y`` by using the currently active color, font and alignment settings. 126 | 127 | :param x: The horizontal position. 128 | :param y: The vertical position. 129 | :param text: The text to write. 130 | 131 | .. versionadded:: 1.0.0 132 | 133 | .. code-block:: 134 | 135 | import flipperzero as f0 136 | 137 | f0.canvas_set_color(f0.COLOR_BLACK) 138 | f0.canvas_set_text_align(f0.ALIGN_CENTER, f0.ALIGN_BEGIN) 139 | f0.canvas_set_text(64, 32, 'Hello World!') 140 | f0.canvas_update() 141 | 142 | .. seealso:: 143 | 144 | * :func:`canvas_set_color` to change the canvas color. 145 | * :func:`canvas_set_text_align` to change the alignment settings. 146 | * :func:`canvas_set_font` to change the current font. 147 | ''' 148 | pass 149 | 150 | def canvas_draw_dot(x: int, y: int) -> None: 151 | ''' 152 | Draw a dot on the canvas by using the currently active color settings. 153 | 154 | :param x: The horizontal position. 155 | :param y: The vertical position. 156 | 157 | .. versionadded:: 1.0.0 158 | ''' 159 | pass 160 | 161 | def canvas_draw_box(x: int, y: int, w: int, h: int, r: int) -> None: 162 | ''' 163 | Draw a box on the canvas. The fill color is defined by the currently active color settings. 164 | Set the corner radius to zero to draw a rectangle without rounded corners. 165 | 166 | :param x: The horizontal position. 167 | :param y: The vertical position. 168 | :param w: The width of the box. 169 | :param h: The height of the box. 170 | :param r: The corner radius to use. 171 | 172 | .. versionadded:: 1.0.0 173 | ''' 174 | pass 175 | 176 | def canvas_draw_frame(x: int, y: int, w: int, h: int, r: int) -> None: 177 | ''' 178 | Draw a frame on the canvas. The border color is defined by the currently active color settings. 179 | Set the corner radius to zero to draw a rectangle without rounded corners. 180 | 181 | :param x: The horizontal position. 182 | :param y: The vertical position. 183 | :param w: The width of the box. 184 | :param h: The height of the box. 185 | :param r: The corner radius to use. 186 | 187 | .. versionadded:: 1.0.0 188 | ''' 189 | pass 190 | 191 | def canvas_draw_line(x0: int, y0: int, x1: int, y1: int) -> None: 192 | ''' 193 | Draw a line on the canvas. The color is defined by the currently active color settings. 194 | 195 | :param x0: The horizontal start position. 196 | :param y0: The vertical start position. 197 | :param x1: The horizontal end position. 198 | :param y1: The vertical end sposition. 199 | 200 | .. versionadded:: 1.0.0 201 | ''' 202 | pass 203 | 204 | def canvas_draw_circle(x: int, y: int, r: int) -> None: 205 | ''' 206 | Draw a circle on the canvas. The border color is defined by the currently active color settings. 207 | 208 | :param x: The horizontal position. 209 | :param y: The vertical position. 210 | :param r: The radius to use. 211 | 212 | .. versionadded:: 1.0.0 213 | ''' 214 | pass 215 | 216 | def canvas_draw_disc(x: int, y: int, r: int) -> None: 217 | ''' 218 | Draw a disc on the canvas. The fill color is defined by the currently active color settings. 219 | 220 | :param x: The horizontal position. 221 | :param y: The vertical position. 222 | :param r: The radius to use. 223 | 224 | .. versionadded:: 1.0.0 225 | ''' 226 | pass 227 | -------------------------------------------------------------------------------- /flipperzero/_dialog.py: -------------------------------------------------------------------------------- 1 | def dialog_message_set_header(text: str, x: int, y: int, h: int, v: int) -> None: 2 | ''' 3 | Set a header text on the dialog box. 4 | 5 | :param text: The text to set. 6 | :param x: The x coordinates to use. 7 | :param y: The y coordinates to use. 8 | :param h: The horizontal alignment. 9 | :param v: The vertical alignment. 10 | 11 | .. versionadded:: 1.0.0 12 | ''' 13 | pass 14 | 15 | def dialog_message_set_text(text: str, x: int, y: int, h: int, v: int) -> None: 16 | ''' 17 | Set a text on the dialog box. 18 | 19 | :param text: The text to set. 20 | :param x: The x coordinates to use. 21 | :param y: The y coordinates to use. 22 | :param h: The horizontal alignment. 23 | :param v: The vertical alignment. 24 | 25 | .. versionadded:: 1.0.0 26 | ''' 27 | pass 28 | 29 | def dialog_message_set_button(text: str, button: int) -> None: 30 | ''' 31 | Set the text of a dialog box button. 32 | 33 | :param text: The text to set. 34 | :param button: The button to use (e.g. :const:`INPUT_BUTTON_UP`). 35 | 36 | .. versionadded:: 1.0.0 37 | ''' 38 | pass 39 | 40 | def dialog_message_show() -> int: 41 | ''' 42 | Display the dialog box with the configured settings. 43 | This function is blocking. 44 | 45 | :returns: The button code, used to close the dialog (e.g. :const:`INPUT_BUTTON_OK`) 46 | 47 | .. versionadded:: 1.0.0 48 | 49 | .. code-block:: 50 | 51 | import flipperzero as f0 52 | 53 | f0.dialog_message_set_header('Important',64, 12) 54 | f0.dialog_message_set_text('It this awesome?', 64, 24) 55 | f0.dialog_message_set_button('Yes', f0.INPUT_BUTTON_LEFT) 56 | f0.dialog_message_set_button('No', f0.INPUT_BUTTON_RIGHT) 57 | 58 | while f0.dialog_message_show() is not f0.INPUT_BUTTON_LEFT: 59 | pass 60 | ''' 61 | pass 62 | -------------------------------------------------------------------------------- /flipperzero/_gpio.py: -------------------------------------------------------------------------------- 1 | from typing import Callable 2 | 3 | GPIO_PIN_PC0: int 4 | ''' 5 | Constant identifier for GPIO pin PC0. 6 | 7 | * This pin can be used as ADC input. 8 | 9 | .. versionadded:: 1.2.0 10 | ''' 11 | 12 | GPIO_PIN_PC1: int 13 | ''' 14 | Constant identifier for GPIO pin PC1. 15 | 16 | * This pin can be used as ADC input. 17 | 18 | .. versionadded:: 1.2.0 19 | ''' 20 | 21 | GPIO_PIN_PC3: int 22 | ''' 23 | Constant identifier for GPIO pin PC3. 24 | 25 | * This pin can be used as ADC input. 26 | 27 | .. versionadded:: 1.2.0 28 | ''' 29 | 30 | GPIO_PIN_PB2: int 31 | ''' 32 | Constant identifier for GPIO pin PB2. 33 | 34 | .. versionadded:: 1.2.0 35 | ''' 36 | 37 | GPIO_PIN_PB3: int 38 | ''' 39 | Constant identifier for GPIO pin PB3. 40 | 41 | .. versionadded:: 1.2.0 42 | ''' 43 | 44 | GPIO_PIN_PA4: int 45 | ''' 46 | Constant identifier for GPIO pin PA4. 47 | 48 | * This pin can be used as ADC input. 49 | * This pin can be used as PWM output. 50 | 51 | .. versionadded:: 1.2.0 52 | ''' 53 | 54 | GPIO_PIN_PA6: int 55 | ''' 56 | Constant identifier for GPIO pin PA6. 57 | 58 | * This pin can be used as ADC input. 59 | 60 | .. versionadded:: 1.2.0 61 | ''' 62 | 63 | GPIO_PIN_PA7: int 64 | ''' 65 | Constant identifier for GPIO pin PA7. 66 | 67 | * This pin can be used as ADC input. 68 | * This pin can be used as PWM output. 69 | * This pin can be used to transmit an infrared signal with an IR LED. 70 | 71 | 72 | .. versionadded:: 1.2.0 73 | ''' 74 | 75 | GPIO_MODE_INPUT: int 76 | ''' 77 | Constant configuration value for the GPIO input mode. 78 | 79 | .. versionadded:: 1.2.0 80 | ''' 81 | 82 | GPIO_MODE_OUTPUT_PUSH_PULL: int 83 | ''' 84 | Constant configuration value for the GPIO output as push-pull mode. 85 | 86 | .. versionadded:: 1.2.0 87 | ''' 88 | 89 | GPIO_MODE_OUTPUT_OPEN_DRAIN: int 90 | ''' 91 | Constant configuration value for the GPIO output as open-drain mode. 92 | 93 | .. versionadded:: 1.2.0 94 | ''' 95 | 96 | GPIO_MODE_ANALOG: int 97 | ''' 98 | Constant configuration value for the GPIO analog mode. 99 | 100 | .. versionadded:: 1.2.0 101 | ''' 102 | 103 | GPIO_MODE_INTERRUPT_RISE: int 104 | ''' 105 | Constant configuration value for the GPIO interrupt on rising edges mode. 106 | 107 | .. versionadded:: 1.2.0 108 | ''' 109 | 110 | GPIO_MODE_INTERRUPT_FALL: int 111 | ''' 112 | Constant configuration value for the GPIO interrupt on falling edges mode. 113 | 114 | .. versionadded:: 1.2.0 115 | ''' 116 | 117 | GPIO_PULL_NO: int 118 | ''' 119 | Constant configuration value for the GPIO internal pull resistor disabled. 120 | 121 | .. versionadded:: 1.2.0 122 | ''' 123 | 124 | GPIO_PULL_UP: int 125 | ''' 126 | Constant configuration value for the GPIO internal pull-up resistor enabled. 127 | 128 | .. versionadded:: 1.2.0 129 | ''' 130 | 131 | GPIO_PULL_DOWN: int 132 | ''' 133 | Constant configuration value for the GPIO internal pull-down resistor enabled. 134 | 135 | .. versionadded:: 1.2.0 136 | ''' 137 | 138 | GPIO_SPEED_LOW: int 139 | ''' 140 | Constant configuration value for the GPIO in low speed. 141 | 142 | .. versionadded:: 1.2.0 143 | ''' 144 | 145 | GPIO_SPEED_MEDIUM: int 146 | ''' 147 | Constant configuration value for the GPIO in medium speed. 148 | 149 | .. versionadded:: 1.2.0 150 | ''' 151 | 152 | GPIO_SPEED_HIGH: int 153 | ''' 154 | Constant configuration value for the GPIO in high speed. 155 | 156 | .. versionadded:: 1.2.0 157 | ''' 158 | 159 | GPIO_SPEED_VERY_HIGH: int 160 | ''' 161 | Constant configuration value for the GPIO in very high speed. 162 | 163 | .. versionadded:: 1.2.0 164 | ''' 165 | 166 | def gpio_init_pin(pin: int, mode: int, pull: int = None, speed: int = None) -> bool: 167 | ''' 168 | Initialize a GPIO pin. 169 | 170 | :param pin: The pin to initialize (e.g. :const:`GPIO_PIN_PA4`). 171 | :param mode: The mode to use (e.g. :const:`GPIO_MODE_INPUT`). 172 | :param pull: The pull resistor to use. Default is :const:`GPIO_PULL_NO`. 173 | :param speed: The speed to use. Default is :const:`GPIO_SPEED_LOW`. 174 | :returns: :const:`True` on success, :const:`False` otherwise. 175 | 176 | .. versionadded:: 1.2.0 177 | .. versionchanged:: 1.3.0 178 | The return value changed from ``None`` to ``bool``. 179 | 180 | .. hint:: 181 | 182 | The interrupt modes :const:`GPIO_MODE_INTERRUPT_RISE` and :const:`GPIO_MODE_INTERRUPT_FALL` can be combined using bitwise OR. 183 | This allows you to handle rising `and` falling edges. 184 | ''' 185 | pass 186 | 187 | def gpio_deinit_pin(pin: int) -> None: 188 | ''' 189 | Deinitialize a GPIO pin. 190 | 191 | :param pin: The pin to deinitialize (e.g. :const:`GPIO_PIN_PA4`). 192 | 193 | .. versionadded:: 1.3.0 194 | 195 | .. note:: 196 | 197 | It's not strictly necessary to deinitialize your GPIO pins upon script termination, this is already covered by the interpreter. 198 | ''' 199 | pass 200 | 201 | def gpio_set_pin(pin: int, state: bool) -> None: 202 | ''' 203 | Set the state of an output pin. 204 | 205 | :param pin: The pin to set (e.g. :const:`GPIO_PIN_PA4`). 206 | :param state: The state to set. 207 | 208 | .. versionadded:: 1.2.0 209 | 210 | .. hint:: 211 | 212 | Don't forget to initialize the pin first. 213 | ''' 214 | pass 215 | 216 | def gpio_get_pin(pin: int) -> bool: 217 | ''' 218 | Read the state of an input pin. 219 | 220 | :param pin: The pin to read (e.g. :const:`GPIO_PIN_PA4`). 221 | :returns: :const:`True` if the pin is high, :const:`False` on a low signal. 222 | 223 | .. versionadded:: 1.2.0 224 | 225 | .. hint:: 226 | 227 | Don't forget to initialize the pin first. 228 | ''' 229 | pass 230 | 231 | def on_gpio() -> Callable[[int], None]: 232 | ''' 233 | Decorate a function to be used as GPIO interrupt handler. The decorated function will be invoked upon a GPIO interrupt. 234 | 235 | .. versionadded:: 1.0.0 236 | 237 | .. code-block:: 238 | 239 | import flipperzero as f0 240 | 241 | f0.gpio_init_pin(f0.GPIO_PIN_PC0, f0.GPIO_MODE_INTERRUPT_RISE, f0.GPIO_PULL_UP) 242 | 243 | @f0.on_gpio 244 | def interrupt_handler(pin): 245 | if pin == f0.GPIO_PIN_PC0: 246 | ... 247 | 248 | .. warning:: 249 | 250 | You can only decorate one function per application. 251 | ''' 252 | pass 253 | -------------------------------------------------------------------------------- /flipperzero/_infrared.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | def infrared_receive(timeout: int = 1000000) -> List[int]: 4 | ''' 5 | Receive an infrared signal. This is a blocking method. 6 | The method blocks until a timeout occurs or the internal 7 | signal buffer (capacity is 1024 timings) is filled. 8 | 9 | :param timeout: The timeout to use in microseconds. 10 | :returns: A list of timings in microseconds, starting with high. 11 | 12 | .. versionadded:: 1.3.0 13 | ''' 14 | pass 15 | 16 | def infrared_transmit(signal: List[int], repeat: int = 1, use_external_pin: bool = False, frequency: int = 38000, duty: float = 0.33) -> bool: 17 | ''' 18 | Transmit an infrared signal. This is a blocking method. 19 | The method blocks until the whole signal is sent. 20 | The signal list has the same format as the return value 21 | of :func:`infrared_receive`. Hence you can directly re-send 22 | a received signal without any further processing. 23 | 24 | :param signal: The signal to use. 25 | :param repeat: How many times the signal should be sent. 26 | :param use_external_pin: :const:`True` to use an external IR LED on GPIO pin :const:`flipperzero.GPIO_PIN_PA7`. 27 | :param frequency: The frequency to use for the PWM signal. 28 | :param duty: The duty cycle to use for the PWM signal. 29 | :returns: :const:`True` on success, :const:`False` otherwise. 30 | 31 | .. versionadded:: 1.3.0 32 | ''' 33 | pass 34 | 35 | def infrared_is_busy() -> bool: 36 | ''' 37 | Check if the infrared subsystem is busy. 38 | 39 | :returns: :const:`True` if occupied, :const:`False` otherwise. 40 | 41 | .. versionadded:: 1.3.0 42 | ''' 43 | pass 44 | -------------------------------------------------------------------------------- /flipperzero/_input.py: -------------------------------------------------------------------------------- 1 | from typing import Callable 2 | 3 | INPUT_BUTTON_UP: int 4 | ''' 5 | Constant value for the `up` button. 6 | 7 | .. versionadded:: 1.0.0 8 | ''' 9 | 10 | INPUT_BUTTON_DOWN: int 11 | ''' 12 | Constant value for the `down` button. 13 | 14 | .. versionadded:: 1.0.0 15 | ''' 16 | 17 | INPUT_BUTTON_RIGHT: int 18 | ''' 19 | Constant value for the `right` button. 20 | 21 | .. versionadded:: 1.0.0 22 | ''' 23 | 24 | INPUT_BUTTON_LEFT: int 25 | ''' 26 | Constant value for the `left` button. 27 | 28 | .. versionadded:: 1.0.0 29 | ''' 30 | 31 | INPUT_BUTTON_OK: int 32 | ''' 33 | Constant value for the `ok` button. 34 | 35 | .. versionadded:: 1.0.0 36 | ''' 37 | 38 | INPUT_BUTTON_BACK: int 39 | ''' 40 | Constant value for the `back` button. 41 | 42 | .. versionadded:: 1.0.0 43 | ''' 44 | 45 | INPUT_TYPE_PRESS: int 46 | ''' 47 | Constant value for the `press` event of a button. 48 | 49 | .. versionadded:: 1.0.0 50 | ''' 51 | 52 | INPUT_TYPE_RELEASE: int 53 | ''' 54 | Constant value for the `release` event of a button. 55 | 56 | .. versionadded:: 1.0.0 57 | ''' 58 | 59 | INPUT_TYPE_SHORT: int 60 | ''' 61 | Constant value for the `short` press event of a button. 62 | 63 | .. versionadded:: 1.0.0 64 | ''' 65 | 66 | INPUT_TYPE_LONG: int 67 | ''' 68 | Constant value for the `long` press event of a button. 69 | 70 | .. versionadded:: 1.0.0 71 | ''' 72 | 73 | INPUT_TYPE_REPEAT: int 74 | ''' 75 | Constant value for the `repeat` press event of a button. 76 | 77 | .. versionadded:: 1.0.0 78 | ''' 79 | 80 | def on_input() -> Callable[[int, int], None]: 81 | ''' 82 | Decorate a function to be used as input handler. The decorated function will be invoked upon interaction with one of the buttons on the Flipper. 83 | 84 | .. versionadded:: 1.0.0 85 | 86 | .. code-block:: 87 | 88 | import flipperzero as f0 89 | 90 | @f0.on_input 91 | def input_handler(button, type): 92 | if button == f0.INPUT_BUTTON_BACK: 93 | if type == f0.INPUT_TYPE_LONG: 94 | ... 95 | 96 | .. warning:: 97 | 98 | You can only decorate one function per application. 99 | ''' 100 | pass 101 | -------------------------------------------------------------------------------- /flipperzero/_light.py: -------------------------------------------------------------------------------- 1 | from typing import Callable 2 | 3 | LIGHT_RED: int 4 | ''' 5 | Constant value for the red LED light. 6 | 7 | .. versionadded:: 1.0.0 8 | ''' 9 | 10 | LIGHT_GREEN: int 11 | ''' 12 | Constant value for the green LED light. 13 | 14 | .. versionadded:: 1.0.0 15 | ''' 16 | 17 | LIGHT_BLUE: int 18 | ''' 19 | Constant value for the blue LED light. 20 | 21 | .. versionadded:: 1.0.0 22 | ''' 23 | 24 | LIGHT_BACKLIGHT: int 25 | ''' 26 | Constant value for the display backlight. 27 | 28 | .. versionadded:: 1.0.0 29 | ''' 30 | 31 | def light_set(light: int, brightness: int) -> None: 32 | ''' 33 | Control the RGB LED on your Flipper. You can also set the brightness of multiple channels at once using bitwise operations. 34 | The ``brightness`` parameter accepts values from 0 (light off) to 255 (very bright). 35 | 36 | :param light: The RGB channels to set. 37 | :param brightness: The brightness to use. 38 | 39 | .. versionadded:: 1.0.0 40 | 41 | .. code-block:: 42 | 43 | import flipperzero as f0 44 | 45 | f0.light_set(f0.LIGHT_RED | f0.LIGHT_GREEN, 250) 46 | 47 | .. tip:: 48 | 49 | You can use up to seven colors using `additive mixing `_. 50 | ''' 51 | pass 52 | 53 | def light_blink_start(light: int, brightness: int, on_time: int, period: int) -> None: 54 | ''' 55 | Let the RGB LED blink. You can define the total duration of a blink period and the duration, the LED is active during a blink period. 56 | Hence, ``on_time`` must be smaller than ``period``. This is a non-blocking operation. The LED will continue to blink until you call :func:`light_blink_stop`. 57 | 58 | :param light: The RGB channels to set. 59 | :param brightness: The brightness to use. 60 | :param on_time: The LED's active duration in milliseconds. 61 | :param period: Total duration of a blink period in milliseconds. 62 | 63 | .. versionadded:: 1.0.0 64 | 65 | .. code-block:: 66 | 67 | import flipperzero as f0 68 | 69 | f0.light_blink_start(f0.LIGHT_RED, 150, 100, 200) 70 | ''' 71 | pass 72 | 73 | def light_blink_set_color(light: int) -> None: 74 | ''' 75 | Change the RGB LED's color while blinking. This is a non-blocking operation. 76 | Be aware, that you must start the blinking procedure first by using the :func:`light_blink_start` function. 77 | Call the :func:`light_blink_stop` function to stop the blinking LED. 78 | 79 | :param light: The RGB channels to set. 80 | 81 | .. versionadded:: 1.0.0 82 | ''' 83 | pass 84 | 85 | def light_blink_stop() -> None: 86 | ''' 87 | Stop the blinking LED. 88 | 89 | .. versionadded:: 1.0.0 90 | ''' 91 | pass 92 | -------------------------------------------------------------------------------- /flipperzero/_pwm.py: -------------------------------------------------------------------------------- 1 | def pwm_start(pin: int, frequency: int, duty: int) -> bool: 2 | ''' 3 | Start or change the PWM signal on the corresponding GPIO pin. 4 | 5 | :param pin: The pin to read (e.g. :const:`GPIO_PIN_PA7`). 6 | :param frequency: The frequency to set in Hz. 7 | :param duty: The duty cycle per period in percent. 8 | :returns: :const:`True` on success, :const:`False` otherwise. 9 | 10 | .. versionadded:: 1.3.0 11 | 12 | .. warning:: 13 | 14 | You don't have to initialize the pin first. 15 | ''' 16 | pass 17 | 18 | def pwm_stop(pin: int) -> None: 19 | ''' 20 | Stop the PWM signal on the corresponding GPIO pin. 21 | 22 | :param pin: The pin to use (e.g. :const:`GPIO_PIN_PA7`). 23 | 24 | .. versionadded:: 1.3.0 25 | ''' 26 | pass 27 | 28 | def pwm_is_running(pin: int) -> bool: 29 | ''' 30 | Check if the corresponding GPIO pin has a PWM signal output. 31 | 32 | :param pin: The pin to check (e.g. :const:`GPIO_PIN_PA7`). 33 | :returns: :const:`True` on success, :const:`False` otherwise. 34 | 35 | .. versionadded:: 1.3.0 36 | ''' 37 | pass 38 | -------------------------------------------------------------------------------- /flipperzero/_uart.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | UART_MODE_LPUART: int 4 | ''' 5 | Constant value for the low power UART mode. 6 | 7 | .. versionadded:: 1.5.0 8 | ''' 9 | 10 | UART_MODE_USART: int 11 | ''' 12 | Constant value for the USART mode. 13 | 14 | .. versionadded:: 1.5.0 15 | ''' 16 | 17 | class UART: 18 | ''' 19 | This represents an UART connection. 20 | The class has no :const:`__init__` method, use :func:`uart_open` to start an UART connection and receive an instance. 21 | 22 | .. versionadded:: 1.5.0 23 | 24 | An :class:`UART` instance is iterable: 25 | 26 | .. code-block:: 27 | 28 | import flipperzero as f0 29 | 30 | with f0.open(f0.UART_MODE_USART, 115200) as uart: 31 | lines = [line for line in uart] 32 | 33 | An :class:`UART` instance can be used with a `context manager `_: 34 | 35 | .. code-block:: 36 | 37 | import flipperzero as f0 38 | 39 | with f0.open(f0.UART_MODE_USART, 115200) as uart: 40 | ... 41 | 42 | .. hint:: 43 | 44 | The read and write methods are non-blocking in terms of data availability. 45 | They don't block code execution upon data is available. 46 | Just an empty result will be returned. 47 | ''' 48 | 49 | def read(self, size: int = -1) -> bytes: 50 | ''' 51 | Read from the connection. 52 | The method will read up to ``size`` bytes and return them. 53 | If ``size`` is not specified, all available data will be returned. 54 | The method will return zero bytes, if no data is available. 55 | 56 | :param size: The maximum number of bytes to read. 57 | :returns: Up to ``size`` bytes. 58 | 59 | .. versionadded:: 1.5.0 60 | ''' 61 | pass 62 | 63 | def readline(self, size: int = -1) -> bytes: 64 | ''' 65 | Read and return one line from the connection. 66 | If ``size`` is specified, at most ``size`` bytes will be read. 67 | The line terminator is always ``b'\\n'``. 68 | 69 | :param size: The maximum number of bytes to read. 70 | :returns: Up to ``size`` bytes. 71 | 72 | .. versionadded:: 1.5.0 73 | ''' 74 | pass 75 | 76 | def readlines(self) -> List[bytes]: 77 | ''' 78 | Read and return a list of lines from the connection. 79 | The line terminator is always ``b'\\n'``. 80 | 81 | :returns: A list of bytes. 82 | 83 | .. versionadded:: 1.5.0 84 | ''' 85 | pass 86 | 87 | def write(self, data: bytes) -> int: 88 | ''' 89 | Write the given bytes to the connection stream. 90 | The number of written bytes will be returned. 91 | This can be less than the length of the provided data. 92 | Be aware, that the data is not sent synchronously. 93 | Call :meth:`flush` if you have to wait for the data to be sent. 94 | 95 | :param data: The data to transmit. 96 | :returns: The number of bytes sent. 97 | 98 | .. versionadded:: 1.5.0 99 | ''' 100 | pass 101 | 102 | def flush(self) -> None: 103 | ''' 104 | Flush the transmission buffer to the underlying UART connection. 105 | This method blocks until all data is sent. 106 | 107 | .. versionadded:: 1.5.0 108 | ''' 109 | pass 110 | 111 | def close(self) -> None: 112 | ''' 113 | Close the UART connection. 114 | 115 | .. versionadded:: 1.5.0 116 | ''' 117 | pass 118 | 119 | def __enter__(self) -> 'UART': 120 | ''' 121 | This method is invoked, when the instance enters a runtime context. 122 | 123 | :returns: The :class:`UART` connection. 124 | 125 | .. versionadded:: 1.5.0 126 | ''' 127 | pass 128 | 129 | def __exit__(self, *args, **kwargs) -> None: 130 | ''' 131 | This method is invoked, when the instance leavs a runtime context. 132 | This basically calls :meth:`close` on the instance. 133 | 134 | .. versionadded:: 1.5.0 135 | ''' 136 | pass 137 | 138 | def __del__(self) -> None: 139 | ''' 140 | This method is invoked, when the garbage collector removes the object. 141 | This basically calls :meth:`close` on the instance. 142 | 143 | .. versionadded:: 1.5.0 144 | ''' 145 | pass 146 | 147 | def uart_open(mode: int, baud_rate: int) -> UART: 148 | ''' 149 | Open a connection to an UART enabled device by using the specified mode and baud rate. 150 | 151 | :param mode: The mode to use, either :const:`UART_MODE_LPUART` or :const:`UART_MODE_USART`. 152 | :param baud_rate: The baud rate to use. 153 | :returns: A :class:`UART` object on success, :const:`None` otherwise. 154 | 155 | .. versionadded:: 1.5.0 156 | 157 | .. code-block:: 158 | 159 | import flipperzero as f0 160 | 161 | with f0.uart_open(f0.UART_MODE_USART, 115200) as uart: 162 | ... 163 | ''' 164 | pass 165 | -------------------------------------------------------------------------------- /flipperzero/_vibro.py: -------------------------------------------------------------------------------- 1 | def vibro_set(state: bool) -> bool: 2 | ''' 3 | Turn vibration on or off. This is a non-blocking operation. The vibration motor will continue to run until you stop it. 4 | 5 | :param state: :const:`True` to turn on vibration. 6 | :returns: :const:`True` if vibration is on. 7 | 8 | .. versionadded:: 1.0.0 9 | ''' 10 | pass 11 | -------------------------------------------------------------------------------- /flipperzero/io.py: -------------------------------------------------------------------------------- 1 | import typing 2 | import io 3 | 4 | _open = io.open 5 | 6 | SEEK_SET: int = 0 7 | ''' 8 | Set the pointer position relative to the beginning of the stream. 9 | 10 | .. versionadded:: 1.5.0 11 | ''' 12 | 13 | SEEK_CUR: int = 1 14 | ''' 15 | Set the pointer position relative to the current position. 16 | 17 | .. versionadded:: 1.5.0 18 | ''' 19 | 20 | SEEK_END: int = 2 21 | ''' 22 | Set the pointer position relative to the end of the stream. 23 | 24 | .. versionadded:: 1.5.0 25 | ''' 26 | 27 | class BinaryFileIO: 28 | ''' 29 | Represents a file, opened in binary mode. 30 | 31 | .. versionadded:: 1.5.0 32 | ''' 33 | 34 | name: str 35 | ''' 36 | The name of the file. 37 | 38 | .. versionadded:: 1.5.0 39 | ''' 40 | 41 | readable: bool 42 | ''' 43 | Read-only attribute, indicating if the file is readable. 44 | 45 | .. versionadded:: 1.5.0 46 | ''' 47 | 48 | writable: bool 49 | ''' 50 | Read-only attribute, indicating if the file is writable. 51 | 52 | .. versionadded:: 1.5.0 53 | ''' 54 | 55 | def read(self, size: int = -1) -> bytes: 56 | ''' 57 | Read from the file. 58 | The method will read up to ``size`` bytes and return them. 59 | If ``size`` is not specified, all content up to EOF will be returned. 60 | If the internal pointer is already at EOF, an empty byte string ``b''`` will be returned. 61 | 62 | :param size: The maximum number of bytes to read. 63 | :returns: Up to ``size`` bytes. 64 | 65 | .. versionadded:: 1.5.0 66 | ''' 67 | pass 68 | 69 | def readline(self, size: int = -1) -> bytes: 70 | ''' 71 | Read and return one line from the file. 72 | If ``size`` is specified, at most ``size`` bytes will be read. 73 | The line terminator is defined as ``b'\\n'``. 74 | The new line character is included in the return value. 75 | If the internal pointer is at EOF, an empty byte string ``b''`` will be returned. 76 | 77 | :param size: The maximum number of bytes to read. 78 | :returns: Up to ``size`` bytes. 79 | 80 | .. versionadded:: 1.5.0 81 | ''' 82 | pass 83 | 84 | def readlines(self) -> typing.List[bytes]: 85 | ''' 86 | Read and return a list of lines from the file. 87 | The line terminator is defined as ``b'\\n'``. 88 | The new line character is included in the return value. 89 | If the internal pointer is at EOF, an empty list will be returned. 90 | 91 | :returns: A list of bytes. 92 | 93 | .. versionadded:: 1.5.0 94 | ''' 95 | pass 96 | 97 | def write(self, data: bytes) -> int: 98 | ''' 99 | Write the given bytes to the file. 100 | The number of written bytes will be returned. 101 | This can be less than the length of the provided data. 102 | 103 | :param data: The data to write. 104 | :returns: The number of bytes written. 105 | 106 | .. versionadded:: 1.5.0 107 | ''' 108 | pass 109 | 110 | def flush(self) -> None: 111 | ''' 112 | Write the contents of the file buffer to the file on the SD card. 113 | 114 | .. versionadded:: 1.5.0 115 | ''' 116 | pass 117 | 118 | def seek(self, offset: int, whence: int = SEEK_SET) -> int: 119 | ''' 120 | Set the pointer position by the given ``offset``, relative to the position indicated by ``whence``. 121 | The new absolute position will be returned. 122 | 123 | :param offset: The offset to use. 124 | :param whence: How to interpret the offset (e.g. :const:`SEEK_SET`). 125 | :returns: The new absolute position. 126 | 127 | .. versionadded:: 1.5.0 128 | ''' 129 | pass 130 | 131 | def tell(self) -> int: 132 | ''' 133 | Get the current pointer position. 134 | 135 | :returns: The absolute position of the pointer. 136 | 137 | .. versionadded:: 1.5.0 138 | ''' 139 | pass 140 | 141 | def close(self) -> None: 142 | ''' 143 | Close the file handle. 144 | 145 | .. versionadded:: 1.5.0 146 | ''' 147 | pass 148 | 149 | def __enter__(self) -> 'BinaryFileIO': 150 | ''' 151 | This method is invoked, when the instance enters a runtime context. 152 | 153 | :returns: The :class:`BinaryFileIO` instance. 154 | 155 | .. versionadded:: 1.5.0 156 | ''' 157 | pass 158 | 159 | def __exit__(self, *args, **kwargs) -> None: 160 | ''' 161 | This method is invoked, when the instance leavs a runtime context. 162 | This basically calls :meth:`close` on the instance. 163 | 164 | .. versionadded:: 1.5.0 165 | ''' 166 | pass 167 | 168 | def __del__(self) -> None: 169 | ''' 170 | This method is invoked, when the garbage collector removes the object. 171 | This basically calls :meth:`close` on the instance. 172 | 173 | .. versionadded:: 1.5.0 174 | ''' 175 | pass 176 | 177 | class TextFileIO: 178 | ''' 179 | Represents a file, opened in text mode. 180 | 181 | .. versionadded:: 1.5.0 182 | ''' 183 | 184 | name: str 185 | ''' 186 | The name of the file. 187 | 188 | .. versionadded:: 1.5.0 189 | ''' 190 | 191 | readable: bool 192 | ''' 193 | Read-only attribute, indicating if the file is readable. 194 | 195 | .. versionadded:: 1.5.0 196 | ''' 197 | 198 | writable: bool 199 | ''' 200 | Read-only attribute, indicating if the file is writable. 201 | 202 | .. versionadded:: 1.5.0 203 | ''' 204 | 205 | def read(self, size: int = -1) -> str: 206 | ''' 207 | Read from the file. 208 | The method will read up to ``size`` characters and return them. 209 | If ``size`` is not specified, all content up to EOF will be returned. 210 | If the internal pointer is already at EOF, an empty string will be returned. 211 | 212 | :param size: The maximum number of characters to read. 213 | :returns: Up to ``size`` characters. 214 | 215 | .. versionadded:: 1.5.0 216 | ''' 217 | pass 218 | 219 | def readline(self, size: int = -1) -> str: 220 | ''' 221 | Read and return one line from the file. 222 | If ``size`` is specified, at most ``size`` characters will be read. 223 | The line terminator is defined as ``'\\n'``. 224 | The new line character is included in the return value. 225 | If the internal pointer is at EOF, an empty string will be returned. 226 | 227 | :param size: The maximum number of characters to read. 228 | :returns: Up to ``size`` characters. 229 | 230 | .. versionadded:: 1.5.0 231 | ''' 232 | pass 233 | 234 | def readlines(self) -> typing.List[str]: 235 | ''' 236 | Read and return a list of lines from the file. 237 | The line terminator is defined as ``'\\n'``. 238 | The new line character is included in the return value. 239 | If the internal pointer is at EOF, an empty list will be returned. 240 | 241 | :returns: A list of strings. 242 | 243 | .. versionadded:: 1.5.0 244 | ''' 245 | pass 246 | 247 | def write(self, data: str) -> int: 248 | ''' 249 | Write the given string to the file. 250 | The number of written characters will be returned. 251 | This can be less than the length of the provided data. 252 | 253 | :param data: The data to write. 254 | :returns: The number of characters written. 255 | 256 | .. versionadded:: 1.5.0 257 | ''' 258 | pass 259 | 260 | def flush(self) -> None: 261 | ''' 262 | Write the contents of the file buffer to the file on the SD card. 263 | 264 | .. versionadded:: 1.5.0 265 | ''' 266 | pass 267 | 268 | def seek(self, offset: int, whence: int = SEEK_SET) -> int: 269 | ''' 270 | Set the pointer position by the given ``offset``, relative to the position indicated by ``whence``. 271 | The new absolute position will be returned. 272 | 273 | :param offset: The offset to use. 274 | :param whence: How to interpret the offset (e.g. :const:`SEEK_SET`). 275 | :returns: The new absolute position. 276 | 277 | .. versionadded:: 1.5.0 278 | ''' 279 | pass 280 | 281 | def tell(self) -> int: 282 | ''' 283 | Get the current pointer position. 284 | 285 | :returns: The absolute position of the pointer. 286 | 287 | .. versionadded:: 1.5.0 288 | ''' 289 | pass 290 | 291 | def close(self) -> None: 292 | ''' 293 | Close the file handle. 294 | 295 | .. versionadded:: 1.5.0 296 | ''' 297 | pass 298 | 299 | def __enter__(self) -> 'TextFileIO': 300 | ''' 301 | This method is invoked, when the instance enters a runtime context. 302 | 303 | :returns: The :class:`BinaryFileIO` instance. 304 | 305 | .. versionadded:: 1.5.0 306 | ''' 307 | pass 308 | 309 | def __exit__(self, *args, **kwargs) -> None: 310 | ''' 311 | This method is invoked, when the instance leavs a runtime context. 312 | This basically calls :meth:`close` on the instance. 313 | 314 | .. versionadded:: 1.5.0 315 | ''' 316 | pass 317 | 318 | def __del__(self) -> None: 319 | ''' 320 | This method is invoked, when the garbage collector removes the object. 321 | This basically calls :meth:`close` on the instance. 322 | 323 | .. versionadded:: 1.5.0 324 | ''' 325 | pass 326 | 327 | def open(path: str, mode: str, *args, **kwargs) -> BinaryFileIO | TextFileIO: 328 | ''' 329 | Open a file on the file system with the specified mode. 330 | The file path must always be absolute, beginning with ``/ext``. 331 | The following modifiers are available: 332 | 333 | .. list-table:: 334 | :header-rows: 1 335 | :width: 90% 336 | 337 | * - Character 338 | - Description 339 | * - ``'r'`` 340 | - Open for reading. 341 | This is the default. 342 | Will fail, if the file not exists. 343 | * - ``'w'`` 344 | - Open for writing, truncating an existing file first. 345 | * - ``'b'`` 346 | - Open the file in binary mode. 347 | The return value will be a :class:`BinaryFileIO` instance. 348 | * - ``'t'`` 349 | - Open the in text mode. 350 | This is the default. 351 | The return value will be a :class:`TextFileIO` instance. 352 | * - ``'+'`` 353 | - Open for reading and writing. 354 | Will create the file, if it not exists. 355 | The pointer will be placed at the end of the file. 356 | 357 | The modifiers can be combined, e.g. ``'rb+'`` would open a file for reading and writing in binary mode. 358 | 359 | :param path: The path to the file to open. 360 | :param mode: How the file should be opened. 361 | :param args: Is ignored at the moment. 362 | :param kwargs: Is ignored at the moment. 363 | 364 | .. versionadded:: 1.5.0 365 | ''' 366 | return io._open(path, mode, *args, **kwargs) 367 | -------------------------------------------------------------------------------- /flipperzero/logging.py: -------------------------------------------------------------------------------- 1 | import typing 2 | 3 | TRACE: int = 6 4 | ''' 5 | Constant value for the `trace` log level. 6 | 7 | .. versionadded:: 1.5.0 8 | ''' 9 | 10 | DEBUG: int = 5 11 | ''' 12 | Constant value for the `debug` log level. 13 | 14 | .. versionadded:: 1.5.0 15 | ''' 16 | 17 | INFO: int = 4 18 | ''' 19 | Constant value for the `info` log level. 20 | 21 | .. versionadded:: 1.5.0 22 | ''' 23 | 24 | WARN: int = 3 25 | ''' 26 | Constant value for the `warn` log level. 27 | 28 | .. versionadded:: 1.5.0 29 | ''' 30 | 31 | ERROR: int = 2 32 | ''' 33 | Constant value for the `error` log level. 34 | 35 | .. versionadded:: 1.5.0 36 | ''' 37 | 38 | NONE: int = 1 39 | ''' 40 | Constant value for logging disabled. 41 | 42 | .. versionadded:: 1.5.0 43 | ''' 44 | 45 | level: int 46 | ''' 47 | The threshold log level, as set by the :func:`setLevel` function. 48 | The initial value is set to the :const:`INFO` level. 49 | 50 | .. versionadded:: 1.5.0 51 | 52 | .. hint:: 53 | 54 | Don't change the value of this variable, use :func:`setLevel` instead. 55 | ''' 56 | 57 | def setLevel(level: int) -> None: 58 | ''' 59 | Set the current log level of the application. 60 | 61 | :param level: The log level to set (e.g. :const:`INFO`). 62 | 63 | .. versionadded:: 1.5.0 64 | 65 | .. hint:: 66 | 67 | This doesn't change the Flipper's effective log level settings. 68 | Check out the Flipper's `documentation `_ for details on this topic. 69 | ''' 70 | pass 71 | 72 | def getEffectiveLevel() -> int: 73 | ''' 74 | Get the effective log level from the Flipper's settings. 75 | 76 | :returns: The effective log level. 77 | 78 | .. versionadded:: 1.5.0 79 | ''' 80 | pass 81 | 82 | def trace(message: str, *args) -> None: 83 | ''' 84 | Log a message with level :const:`TRACE`. 85 | The ``message`` argument can be a format string with ``%`` placeholders. 86 | No % formatting operation is performed when ``args`` is empty. 87 | 88 | :param message: The message to log. 89 | :param args: Values for the % formatting. 90 | 91 | .. versionadded:: 1.5.0 92 | 93 | .. code-block:: 94 | 95 | import logging 96 | 97 | value = 42 98 | 99 | logging.trace('value is %d', value) 100 | ''' 101 | pass 102 | 103 | def debug(message: str, *args) -> None: 104 | ''' 105 | Log a message with level :const:`DEBUG`. 106 | See :func:`trace` for details on the usage. 107 | 108 | :param message: The message to log. 109 | :param args: Values for the % formatting. 110 | 111 | .. versionadded:: 1.5.0 112 | ''' 113 | pass 114 | 115 | def info(message: str, *args) -> None: 116 | ''' 117 | Log a message with level :const:`INFO`. 118 | See :func:`trace` for details on the usage. 119 | 120 | :param message: The message to log. 121 | :param args: Values for the % formatting. 122 | 123 | .. versionadded:: 1.5.0 124 | ''' 125 | pass 126 | 127 | def warn(message: str, *args) -> None: 128 | ''' 129 | Log a message with level :const:`WARN`. 130 | See :func:`trace` for details on the usage. 131 | 132 | :param message: The message to log. 133 | :param args: Values for the % formatting. 134 | 135 | .. versionadded:: 1.5.0 136 | ''' 137 | pass 138 | 139 | def error(message: str, *args) -> None: 140 | ''' 141 | Log a message with level :const:`ERROR`. 142 | See :func:`trace` for details on the usage. 143 | 144 | :param message: The message to log. 145 | :param args: Values for the % formatting. 146 | 147 | .. versionadded:: 1.5.0 148 | ''' 149 | pass 150 | 151 | def log(level: int, message: str, *args) -> None: 152 | ''' 153 | Log a message with the given log level. 154 | See :func:`trace` for details on the usage. 155 | 156 | :param level: The log level to use (e.g. :const:`INFO`). 157 | :param message: The message to log. 158 | :param args: Values for the % formatting. 159 | 160 | .. versionadded:: 1.5.0 161 | ''' 162 | pass 163 | -------------------------------------------------------------------------------- /flipperzero/random.py: -------------------------------------------------------------------------------- 1 | def random() -> float: 2 | ''' 3 | Get a random float value between 0.0 (inclusive) and 1.0 (exclusive). 4 | 5 | :returns: The random value. 6 | 7 | .. versionadded:: 1.6.0 8 | ''' 9 | pass 10 | 11 | def randrange(start: int, stop: int, step: int = 1) -> int: 12 | ''' 13 | Get a random integer between ``start`` (inclusive) and ``stop`` 14 | (exclusive) with an optional ``step`` between the values. 15 | 16 | :param start: The start value. 17 | :param stop: The end value. 18 | :param step: The optional step value. 19 | :returns: The random value. 20 | 21 | .. versionadded:: 1.6.0 22 | 23 | .. hint:: 24 | 25 | This function does only generate integer values. 26 | ''' 27 | pass 28 | 29 | def randint(a: int, b: int) -> int: 30 | ''' 31 | Get a random integer between ``a`` (inclusive) and ``b`` (inclusive). 32 | 33 | :param a: The lower value. 34 | :param b: The upper value. 35 | :returns: The random value. 36 | 37 | .. versionadded:: 1.6.0 38 | ''' 39 | pass 40 | 41 | def choice[T](seq: list[T]) -> T: 42 | ''' 43 | Get a random element from the provided sequence. 44 | 45 | :param seq: The sequence to use. 46 | :returns: A random element. 47 | 48 | .. versionadded:: 1.6.0 49 | ''' 50 | pass 51 | 52 | def getrandbits(k: int) -> int: 53 | ''' 54 | Get ``k`` random bits. 55 | 56 | :param k: The number of bits. 57 | :returns: The random bits. 58 | 59 | .. versionadded:: 1.6.0 60 | ''' 61 | pass 62 | 63 | def uniform(a: float, b: float) -> float: 64 | ''' 65 | Get a random float value between ``a`` (inclusive) and ``b`` (inclusive). 66 | 67 | :param a: The lower value. 68 | :param b: The upper value. 69 | :returns: The random value. 70 | 71 | .. versionadded:: 1.6.0 72 | ''' 73 | pass 74 | 75 | def seed(a: int) -> None: 76 | ''' 77 | Initialize the random number generator. 78 | 79 | :param a: The initialization value to use. 80 | 81 | .. versionadded:: 1.6.0 82 | 83 | .. hint:: 84 | 85 | Random generator seeding is done automatically, so there is no need to call this function. 86 | ''' 87 | pass 88 | -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ofabel/mp-flipper/602080eca907cad035f570541384137d786ad52a/icon.png -------------------------------------------------------------------------------- /images/ButtonCenter_7x7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ofabel/mp-flipper/602080eca907cad035f570541384137d786ad52a/images/ButtonCenter_7x7.png -------------------------------------------------------------------------------- /images/Pin_back_arrow_10x8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ofabel/mp-flipper/602080eca907cad035f570541384137d786ad52a/images/Pin_back_arrow_10x8.png -------------------------------------------------------------------------------- /images/qrcode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ofabel/mp-flipper/602080eca907cad035f570541384137d786ad52a/images/qrcode.png -------------------------------------------------------------------------------- /images/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ofabel/mp-flipper/602080eca907cad035f570541384137d786ad52a/images/splash.png -------------------------------------------------------------------------------- /lib/micropython-port/mp_flipper_context.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | #define MP_FLIPPER_GPIO_PIN_OFF (1 << 15) 11 | #define MP_FLIPPER_GPIO_PIN_BLOCKED (1 << 7) 12 | #define MP_FLIPPER_GPIO_PIN_PWM ((MP_FLIPPER_GPIO_PIN_BLOCKED) | (1 << 8)) 13 | 14 | typedef uint16_t mp_flipper_gpio_pin_t; 15 | 16 | #define MP_FLIPPER_INFRARED_RX_BUFFER_SIZE (1024) 17 | 18 | typedef struct { 19 | uint16_t size; 20 | uint32_t* buffer; 21 | uint16_t pointer; 22 | bool running; 23 | } mp_flipper_infrared_rx_t; 24 | 25 | typedef struct { 26 | size_t size; 27 | void* signal; 28 | size_t index; 29 | uint32_t repeat; 30 | bool level; 31 | mp_flipper_infrared_signal_tx_provider provider; 32 | } mp_flipper_infrared_tx_t; 33 | 34 | typedef struct { 35 | Gui* gui; 36 | ViewPort* view_port; 37 | Canvas* canvas; 38 | FuriPubSub* input_event_queue; 39 | FuriPubSubSubscription* input_event; 40 | DialogMessage* dialog_message; 41 | const char* dialog_message_button_left; 42 | const char* dialog_message_button_center; 43 | const char* dialog_message_button_right; 44 | Storage* storage; 45 | FuriHalAdcHandle* adc_handle; 46 | mp_flipper_gpio_pin_t* gpio_pins; 47 | mp_flipper_infrared_rx_t* infrared_rx; 48 | mp_flipper_infrared_tx_t* infrared_tx; 49 | } mp_flipper_context_t; 50 | -------------------------------------------------------------------------------- /lib/micropython-port/mp_flipper_file_helper.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | #include "mp_flipper_context.h" 10 | 11 | mp_flipper_import_stat_t mp_flipper_try_resolve_filesystem_path(FuriString* path) { 12 | mp_flipper_context_t* ctx = mp_flipper_context; 13 | 14 | const char* path_str = furi_string_get_cstr(path); 15 | FuriString* _path = furi_string_alloc_printf("%s", path_str); 16 | 17 | mp_flipper_import_stat_t stat = MP_FLIPPER_IMPORT_STAT_FILE; 18 | 19 | FS_Error error; 20 | FileInfo info; 21 | 22 | do { 23 | // make path absolute 24 | if(!furi_string_start_with_str(_path, "/")) { 25 | furi_string_printf(_path, "%s/%s", mp_flipper_root_module_path, path_str); 26 | } 27 | 28 | // check if file or folder exists 29 | error = storage_common_stat(ctx->storage, furi_string_get_cstr(_path), &info); 30 | if(error == FSE_OK) { 31 | break; 32 | } 33 | 34 | // check for existing python file 35 | furi_string_cat_str(_path, ".py"); 36 | 37 | error = storage_common_stat(ctx->storage, furi_string_get_cstr(_path), &info); 38 | if(error == FSE_OK) { 39 | break; 40 | } 41 | } while(false); 42 | 43 | // file or folder missing 44 | if(error == FSE_NOT_EXIST) { 45 | stat = MP_FLIPPER_IMPORT_STAT_NO_EXIST; 46 | } 47 | // abort on error 48 | else if(error != FSE_OK) { 49 | mp_flipper_raise_os_error_with_filename(MP_ENOENT, furi_string_get_cstr(path)); 50 | } 51 | // path points to directory 52 | else if((info.flags & FSF_DIRECTORY) == FSF_DIRECTORY) { 53 | stat = MP_FLIPPER_IMPORT_STAT_DIR; 54 | } 55 | 56 | furi_string_move(path, _path); 57 | 58 | return stat; 59 | } 60 | -------------------------------------------------------------------------------- /lib/micropython-port/mp_flipper_file_helper.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | mp_flipper_import_stat_t mp_flipper_try_resolve_filesystem_path(FuriString* path); -------------------------------------------------------------------------------- /lib/micropython-port/mp_flipper_file_reader.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | #include 9 | #include 10 | 11 | #include "mp_flipper_context.h" 12 | #include "mp_flipper_file_helper.h" 13 | 14 | typedef struct { 15 | size_t pointer; 16 | FuriString* content; 17 | size_t size; 18 | } FileDescriptor; 19 | 20 | inline void* mp_flipper_file_reader_context_alloc(const char* filename) { 21 | mp_flipper_context_t* ctx = mp_flipper_context; 22 | 23 | FuriString* path = furi_string_alloc_printf("%s", filename); 24 | File* file = storage_file_alloc(ctx->storage); 25 | FileDescriptor* fd = NULL; 26 | 27 | do { 28 | if(mp_flipper_try_resolve_filesystem_path(path) == MP_FLIPPER_IMPORT_STAT_NO_EXIST) { 29 | mp_flipper_raise_os_error_with_filename(MP_ENOENT, filename); 30 | 31 | break; 32 | } 33 | 34 | if(!storage_file_open(file, furi_string_get_cstr(path), FSAM_READ, FSOM_OPEN_EXISTING)) { 35 | mp_flipper_raise_os_error_with_filename(MP_ENOENT, filename); 36 | 37 | break; 38 | } 39 | 40 | fd = malloc(sizeof(FileDescriptor)); 41 | 42 | fd->pointer = 0; 43 | fd->content = furi_string_alloc(); 44 | fd->size = storage_file_size(file); 45 | 46 | char character = '\0'; 47 | 48 | for(size_t i = 0; i < fd->size; i++) { 49 | storage_file_read(file, &character, 1); 50 | 51 | furi_string_push_back(fd->content, character); 52 | } 53 | } while(false); 54 | 55 | storage_file_free(file); 56 | furi_string_free(path); 57 | 58 | return fd; 59 | } 60 | 61 | inline uint32_t mp_flipper_file_reader_read(void* data) { 62 | FileDescriptor* fd = data; 63 | 64 | if(fd->pointer >= fd->size) { 65 | return MP_FLIPPER_FILE_READER_EOF; 66 | } 67 | 68 | return furi_string_get_char(fd->content, fd->pointer++); 69 | } 70 | 71 | void mp_flipper_file_reader_close(void* data) { 72 | FileDescriptor* fd = data; 73 | 74 | furi_string_free(fd->content); 75 | 76 | free(data); 77 | } 78 | -------------------------------------------------------------------------------- /lib/micropython-port/mp_flipper_fileio.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | 7 | #include "mp_flipper_context.h" 8 | #include "mp_flipper_file_helper.h" 9 | 10 | uint8_t MP_FLIPPER_FILE_ACCESS_MODE_READ = FSAM_READ; 11 | uint8_t MP_FLIPPER_FILE_ACCESS_MODE_WRITE = FSAM_WRITE; 12 | 13 | uint8_t MP_FLIPPER_FILE_OPEN_MODE_OPEN_EXIST = FSOM_OPEN_EXISTING; 14 | uint8_t MP_FLIPPER_FILE_OPEN_MODE_OPEN_ALWAYS = FSOM_OPEN_ALWAYS; 15 | uint8_t MP_FLIPPER_FILE_OPEN_MODE_OPEN_APPEND = FSOM_OPEN_APPEND; 16 | uint8_t MP_FLIPPER_FILE_OPEN_MODE_CREATE_NEW = FSOM_CREATE_NEW; 17 | uint8_t MP_FLIPPER_FILE_OPEN_MODE_CREATE_ALWAYS = FSOM_CREATE_ALWAYS; 18 | 19 | inline void* mp_flipper_file_open(const char* name, uint8_t access_mode, uint8_t open_mode) { 20 | mp_flipper_context_t* ctx = mp_flipper_context; 21 | 22 | File* file = storage_file_alloc(ctx->storage); 23 | FuriString* path = furi_string_alloc_set_str(name); 24 | 25 | do { 26 | if(mp_flipper_try_resolve_filesystem_path(path) == MP_FLIPPER_IMPORT_STAT_NO_EXIST) { 27 | break; 28 | } 29 | 30 | if(!storage_file_open(file, furi_string_get_cstr(path), access_mode, open_mode)) { 31 | break; 32 | } 33 | } while(false); 34 | 35 | if(!storage_file_is_open(file)) { 36 | storage_file_close(file); 37 | storage_file_free(file); 38 | 39 | return NULL; 40 | } 41 | 42 | return file; 43 | } 44 | 45 | inline bool mp_flipper_file_close(void* handle) { 46 | File* file = handle; 47 | 48 | bool success = storage_file_is_open(file) && storage_file_close(file); 49 | 50 | storage_file_free(file); 51 | 52 | return success; 53 | } 54 | 55 | inline size_t mp_flipper_file_seek(void* handle, uint32_t offset) { 56 | return storage_file_seek(handle, offset, true); 57 | } 58 | 59 | inline size_t mp_flipper_file_tell(void* handle) { 60 | return storage_file_tell(handle); 61 | } 62 | 63 | inline size_t mp_flipper_file_size(void* handle) { 64 | return storage_file_size(handle); 65 | } 66 | 67 | inline bool mp_flipper_file_sync(void* handle) { 68 | return storage_file_sync(handle); 69 | } 70 | 71 | inline bool mp_flipper_file_eof(void* handle) { 72 | return storage_file_eof(handle); 73 | } 74 | 75 | inline size_t mp_flipper_file_read(void* handle, void* buffer, size_t size, int* errcode) { 76 | File* file = handle; 77 | 78 | *errcode = 0; // TODO handle error 79 | 80 | return storage_file_read(file, buffer, size); 81 | } 82 | 83 | inline size_t mp_flipper_file_write(void* handle, const void* buffer, size_t size, int* errcode) { 84 | File* file = handle; 85 | 86 | *errcode = 0; // TODO handle error 87 | 88 | return storage_file_write(file, buffer, size); 89 | } 90 | -------------------------------------------------------------------------------- /lib/micropython-port/mp_flipper_halport.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | #include "mp_flipper_file_helper.h" 10 | 11 | inline void mp_flipper_stdout_tx_str(const char* str) { 12 | furi_thread_stdout_write(str, strlen(str)); 13 | } 14 | 15 | inline void mp_flipper_stdout_tx_strn_cooked(const char* str, size_t len) { 16 | furi_thread_stdout_write(str, len); 17 | } 18 | 19 | inline mp_flipper_import_stat_t mp_flipper_import_stat(const char* path) { 20 | FuriString* file_path = furi_string_alloc_printf("%s", path); 21 | 22 | mp_flipper_import_stat_t stat = mp_flipper_try_resolve_filesystem_path(file_path); 23 | 24 | stat = furi_string_end_with_str(file_path, path) ? stat : MP_FLIPPER_IMPORT_STAT_NO_EXIST; 25 | 26 | furi_string_free(file_path); 27 | 28 | return stat; 29 | } 30 | 31 | inline size_t mp_flipper_gc_get_max_new_split(void) { 32 | return memmgr_heap_get_max_free_block(); 33 | } 34 | -------------------------------------------------------------------------------- /lib/micropython-port/mp_flipper_logging.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | #include "mp_flipper_context.h" 7 | 8 | static inline FuriLogLevel decode_log_level(uint8_t level) { 9 | switch(level) { 10 | case MP_FLIPPER_LOG_LEVEL_TRACE: 11 | return FuriLogLevelTrace; 12 | case MP_FLIPPER_LOG_LEVEL_DEBUG: 13 | return FuriLogLevelDebug; 14 | case MP_FLIPPER_LOG_LEVEL_INFO: 15 | return FuriLogLevelInfo; 16 | case MP_FLIPPER_LOG_LEVEL_WARN: 17 | return FuriLogLevelWarn; 18 | case MP_FLIPPER_LOG_LEVEL_ERROR: 19 | return FuriLogLevelError; 20 | case MP_FLIPPER_LOG_LEVEL_NONE: 21 | return FuriLogLevelNone; 22 | default: 23 | return FuriLogLevelNone; 24 | } 25 | } 26 | 27 | inline uint8_t mp_flipper_log_get_effective_level() { 28 | switch(furi_log_get_level()) { 29 | case FuriLogLevelTrace: 30 | return MP_FLIPPER_LOG_LEVEL_TRACE; 31 | case FuriLogLevelDebug: 32 | return MP_FLIPPER_LOG_LEVEL_DEBUG; 33 | case FuriLogLevelInfo: 34 | return MP_FLIPPER_LOG_LEVEL_INFO; 35 | case FuriLogLevelWarn: 36 | return MP_FLIPPER_LOG_LEVEL_WARN; 37 | case FuriLogLevelError: 38 | return MP_FLIPPER_LOG_LEVEL_ERROR; 39 | case FuriLogLevelNone: 40 | return MP_FLIPPER_LOG_LEVEL_NONE; 41 | default: 42 | return MP_FLIPPER_LOG_LEVEL_NONE; 43 | } 44 | } 45 | 46 | inline void mp_flipper_log(uint8_t raw_level, const char* message) { 47 | FuriLogLevel level = decode_log_level(raw_level); 48 | 49 | furi_log_print_format(level, "uPython", message); 50 | } 51 | -------------------------------------------------------------------------------- /lib/micropython-port/mp_flipper_modflipperzero_adc.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | #include "mp_flipper_context.h" 7 | 8 | inline static FuriHalAdcChannel decode_pin_to_adc_channel(uint8_t pin) { 9 | switch(pin) { 10 | case MP_FLIPPER_GPIO_PIN_PC0: 11 | return FuriHalAdcChannel1; 12 | case MP_FLIPPER_GPIO_PIN_PC1: 13 | return FuriHalAdcChannel2; 14 | case MP_FLIPPER_GPIO_PIN_PC3: 15 | return FuriHalAdcChannel4; 16 | case MP_FLIPPER_GPIO_PIN_PA4: 17 | return FuriHalAdcChannel9; 18 | case MP_FLIPPER_GPIO_PIN_PA6: 19 | return FuriHalAdcChannel11; 20 | case MP_FLIPPER_GPIO_PIN_PA7: 21 | return FuriHalAdcChannel12; 22 | default: 23 | return FuriHalAdcChannelNone; 24 | } 25 | } 26 | 27 | inline uint16_t mp_flipper_adc_read_pin(uint8_t raw_pin) { 28 | mp_flipper_context_t* ctx = mp_flipper_context; 29 | 30 | FuriHalAdcChannel channel = decode_pin_to_adc_channel(raw_pin); 31 | 32 | if(channel == FuriHalAdcChannelNone) { 33 | return 0; 34 | } 35 | 36 | if(ctx->gpio_pins[raw_pin] != MP_FLIPPER_GPIO_MODE_ANALOG) { 37 | return 0; 38 | } 39 | 40 | return ctx->adc_handle ? furi_hal_adc_read(ctx->adc_handle, channel) : 0; 41 | } 42 | 43 | inline float mp_flipper_adc_convert_to_voltage(uint16_t value) { 44 | mp_flipper_context_t* ctx = mp_flipper_context; 45 | 46 | return ctx->adc_handle ? furi_hal_adc_convert_to_voltage(ctx->adc_handle, value) : 0.0f; 47 | } 48 | -------------------------------------------------------------------------------- /lib/micropython-port/mp_flipper_modflipperzero_canvas.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | 7 | #include "mp_flipper_context.h" 8 | 9 | static Align text_align_x = AlignLeft; 10 | static Align text_align_y = AlignTop; 11 | 12 | inline uint8_t mp_flipper_canvas_width() { 13 | mp_flipper_context_t* ctx = mp_flipper_context; 14 | 15 | return canvas_width(ctx->canvas); 16 | } 17 | 18 | inline uint8_t mp_flipper_canvas_height() { 19 | mp_flipper_context_t* ctx = mp_flipper_context; 20 | 21 | return canvas_height(ctx->canvas); 22 | } 23 | 24 | inline uint8_t mp_flipper_canvas_text_width(const char* text) { 25 | mp_flipper_context_t* ctx = mp_flipper_context; 26 | 27 | return canvas_string_width(ctx->canvas, text); 28 | } 29 | 30 | inline uint8_t mp_flipper_canvas_text_height() { 31 | mp_flipper_context_t* ctx = mp_flipper_context; 32 | 33 | return canvas_current_font_height(ctx->canvas); 34 | } 35 | 36 | inline void mp_flipper_canvas_draw_dot(uint8_t x, uint8_t y) { 37 | mp_flipper_context_t* ctx = mp_flipper_context; 38 | 39 | canvas_draw_dot(ctx->canvas, x, y); 40 | } 41 | 42 | inline void mp_flipper_canvas_draw_box(uint8_t x, uint8_t y, uint8_t w, uint8_t h, uint8_t r) { 43 | mp_flipper_context_t* ctx = mp_flipper_context; 44 | 45 | canvas_draw_rbox(ctx->canvas, x, y, w, h, r); 46 | } 47 | 48 | inline void mp_flipper_canvas_draw_frame(uint8_t x, uint8_t y, uint8_t w, uint8_t h, uint8_t r) { 49 | mp_flipper_context_t* ctx = mp_flipper_context; 50 | 51 | canvas_draw_rframe(ctx->canvas, x, y, w, h, r); 52 | } 53 | 54 | inline void mp_flipper_canvas_draw_line(uint8_t x0, uint8_t y0, uint8_t x1, uint8_t y1) { 55 | mp_flipper_context_t* ctx = mp_flipper_context; 56 | 57 | canvas_draw_line(ctx->canvas, x0, y0, x1, y1); 58 | } 59 | 60 | inline void mp_flipper_canvas_draw_circle(uint8_t x, uint8_t y, uint8_t r) { 61 | mp_flipper_context_t* ctx = mp_flipper_context; 62 | 63 | canvas_draw_circle(ctx->canvas, x, y, r); 64 | } 65 | 66 | inline void mp_flipper_canvas_draw_disc(uint8_t x, uint8_t y, uint8_t r) { 67 | mp_flipper_context_t* ctx = mp_flipper_context; 68 | 69 | canvas_draw_disc(ctx->canvas, x, y, r); 70 | } 71 | 72 | inline void mp_flipper_canvas_set_font(uint8_t font) { 73 | mp_flipper_context_t* ctx = mp_flipper_context; 74 | 75 | canvas_set_font(ctx->canvas, font == MP_FLIPPER_FONT_PRIMARY ? FontPrimary : FontSecondary); 76 | } 77 | 78 | inline void mp_flipper_canvas_set_color(uint8_t color) { 79 | mp_flipper_context_t* ctx = mp_flipper_context; 80 | 81 | canvas_set_color(ctx->canvas, color == MP_FLIPPER_COLOR_BLACK ? ColorBlack : ColorWhite); 82 | } 83 | 84 | inline void mp_flipper_canvas_set_text(uint8_t x, uint8_t y, const char* text) { 85 | mp_flipper_context_t* ctx = mp_flipper_context; 86 | 87 | canvas_draw_str_aligned(ctx->canvas, x, y, text_align_x, text_align_y, text); 88 | } 89 | 90 | inline void mp_flipper_canvas_set_text_align(uint8_t x, uint8_t y) { 91 | Align align_x = x == MP_FLIPPER_ALIGN_BEGIN ? AlignLeft : AlignRight; 92 | Align align_y = y == MP_FLIPPER_ALIGN_BEGIN ? AlignTop : AlignBottom; 93 | 94 | text_align_x = x == MP_FLIPPER_ALIGN_CENTER ? AlignCenter : align_x; 95 | text_align_y = y == MP_FLIPPER_ALIGN_CENTER ? AlignCenter : align_y; 96 | } 97 | 98 | inline void mp_flipper_canvas_update() { 99 | mp_flipper_context_t* ctx = mp_flipper_context; 100 | 101 | canvas_commit(ctx->canvas); 102 | } 103 | 104 | inline void mp_flipper_canvas_clear() { 105 | mp_flipper_context_t* ctx = mp_flipper_context; 106 | 107 | canvas_clear(ctx->canvas); 108 | } 109 | -------------------------------------------------------------------------------- /lib/micropython-port/mp_flipper_modflipperzero_dialog.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | #include "mp_flipper_context.h" 7 | 8 | void mp_flipper_dialog_message_set_text( 9 | const char* text, 10 | uint8_t x, 11 | uint8_t y, 12 | uint8_t h, 13 | uint8_t v) { 14 | mp_flipper_context_t* ctx = mp_flipper_context; 15 | 16 | Align align_x = h == MP_FLIPPER_ALIGN_BEGIN ? AlignLeft : AlignRight; 17 | Align align_y = v == MP_FLIPPER_ALIGN_BEGIN ? AlignTop : AlignBottom; 18 | 19 | align_x = h == MP_FLIPPER_ALIGN_CENTER ? AlignCenter : align_x; 20 | align_y = v == MP_FLIPPER_ALIGN_CENTER ? AlignCenter : align_y; 21 | 22 | dialog_message_set_text(ctx->dialog_message, text, x, y, align_x, align_y); 23 | } 24 | 25 | void mp_flipper_dialog_message_set_header( 26 | const char* text, 27 | uint8_t x, 28 | uint8_t y, 29 | uint8_t h, 30 | uint8_t v) { 31 | mp_flipper_context_t* ctx = mp_flipper_context; 32 | 33 | Align align_x = h == MP_FLIPPER_ALIGN_BEGIN ? AlignLeft : AlignRight; 34 | Align align_y = v == MP_FLIPPER_ALIGN_BEGIN ? AlignTop : AlignBottom; 35 | 36 | align_x = h == MP_FLIPPER_ALIGN_CENTER ? AlignCenter : align_x; 37 | align_y = v == MP_FLIPPER_ALIGN_CENTER ? AlignCenter : align_y; 38 | 39 | dialog_message_set_header(ctx->dialog_message, text, x, y, align_x, align_y); 40 | } 41 | 42 | void mp_flipper_dialog_message_set_button(const char* text, uint8_t button) { 43 | mp_flipper_context_t* ctx = mp_flipper_context; 44 | 45 | // left button 46 | if(button == MP_FLIPPER_INPUT_BUTTON_LEFT) { 47 | ctx->dialog_message_button_left = text; 48 | } 49 | // center button 50 | else if(button == MP_FLIPPER_INPUT_BUTTON_OK) { 51 | ctx->dialog_message_button_center = text; 52 | } 53 | // right button 54 | else if(button == MP_FLIPPER_INPUT_BUTTON_RIGHT) { 55 | ctx->dialog_message_button_right = text; 56 | } 57 | 58 | dialog_message_set_buttons( 59 | ctx->dialog_message, 60 | ctx->dialog_message_button_left, 61 | ctx->dialog_message_button_center, 62 | ctx->dialog_message_button_right); 63 | } 64 | 65 | uint8_t mp_flipper_dialog_message_show() { 66 | mp_flipper_context_t* ctx = mp_flipper_context; 67 | 68 | gui_direct_draw_release(ctx->gui); 69 | 70 | DialogsApp* dialog = furi_record_open(RECORD_DIALOGS); 71 | 72 | uint8_t button = dialog_message_show(dialog, ctx->dialog_message); 73 | 74 | furi_record_close(RECORD_DIALOGS); 75 | 76 | ctx->canvas = gui_direct_draw_acquire(ctx->gui); 77 | 78 | switch(button) { 79 | case DialogMessageButtonLeft: 80 | return MP_FLIPPER_INPUT_BUTTON_LEFT; 81 | case DialogMessageButtonCenter: 82 | return MP_FLIPPER_INPUT_BUTTON_OK; 83 | case DialogMessageButtonRight: 84 | return MP_FLIPPER_INPUT_BUTTON_RIGHT; 85 | case DialogMessageButtonBack: 86 | default: 87 | return MP_FLIPPER_INPUT_BUTTON_BACK; 88 | } 89 | } 90 | 91 | void mp_flipper_dialog_message_clear() { 92 | mp_flipper_context_t* ctx = mp_flipper_context; 93 | 94 | dialog_message_free(ctx->dialog_message); 95 | 96 | ctx->dialog_message = dialog_message_alloc(); 97 | 98 | ctx->dialog_message_button_left = NULL; 99 | ctx->dialog_message_button_center = NULL; 100 | ctx->dialog_message_button_right = NULL; 101 | } 102 | -------------------------------------------------------------------------------- /lib/micropython-port/mp_flipper_modflipperzero_gpio.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | #include "mp_flipper_context.h" 7 | 8 | #define NO_VALUE (-1) 9 | 10 | static const GpioPin* decode_pin(uint8_t pin) { 11 | switch(pin) { 12 | case MP_FLIPPER_GPIO_PIN_PC0: 13 | return &gpio_ext_pc0; 14 | case MP_FLIPPER_GPIO_PIN_PC1: 15 | return &gpio_ext_pc1; 16 | case MP_FLIPPER_GPIO_PIN_PC3: 17 | return &gpio_ext_pc3; 18 | case MP_FLIPPER_GPIO_PIN_PB2: 19 | return &gpio_ext_pb2; 20 | case MP_FLIPPER_GPIO_PIN_PB3: 21 | return &gpio_ext_pb3; 22 | case MP_FLIPPER_GPIO_PIN_PA4: 23 | return &gpio_ext_pa4; 24 | case MP_FLIPPER_GPIO_PIN_PA6: 25 | return &gpio_ext_pa6; 26 | case MP_FLIPPER_GPIO_PIN_PA7: 27 | return &gpio_ext_pa7; 28 | default: 29 | return NULL; 30 | } 31 | } 32 | 33 | static inline const GpioMode decode_mode(uint8_t mode) { 34 | switch(mode) { 35 | case MP_FLIPPER_GPIO_MODE_INPUT: 36 | return GpioModeInput; 37 | case MP_FLIPPER_GPIO_MODE_OUTPUT_PUSH_PULL: 38 | return GpioModeOutputPushPull; 39 | case MP_FLIPPER_GPIO_MODE_OUTPUT_OPEN_DRAIN: 40 | return GpioModeOutputOpenDrain; 41 | case MP_FLIPPER_GPIO_MODE_ANALOG: 42 | return GpioModeAnalog; 43 | case MP_FLIPPER_GPIO_MODE_INTERRUPT_FALL: 44 | return GpioModeInterruptFall; 45 | case MP_FLIPPER_GPIO_MODE_INTERRUPT_RISE: 46 | return GpioModeInterruptRise; 47 | } 48 | 49 | if((mode & MP_FLIPPER_GPIO_MODE_INTERRUPT_FALL) && 50 | (mode & MP_FLIPPER_GPIO_MODE_INTERRUPT_RISE)) { 51 | return GpioModeInterruptRiseFall; 52 | } 53 | 54 | return NO_VALUE; 55 | } 56 | 57 | static inline const GpioPull decode_pull(uint8_t pull) { 58 | switch(pull) { 59 | case MP_FLIPPER_GPIO_PULL_NO: 60 | return GpioPullNo; 61 | case MP_FLIPPER_GPIO_PULL_UP: 62 | return GpioPullUp; 63 | case MP_FLIPPER_GPIO_PULL_DOWN: 64 | return GpioPullDown; 65 | default: 66 | return NO_VALUE; 67 | } 68 | } 69 | 70 | static inline const GpioSpeed decode_speed(uint8_t speed) { 71 | switch(speed) { 72 | case MP_FLIPPER_GPIO_SPEED_LOW: 73 | return GpioSpeedLow; 74 | case MP_FLIPPER_GPIO_SPEED_MEDIUM: 75 | return GpioSpeedMedium; 76 | case MP_FLIPPER_GPIO_SPEED_HIGH: 77 | return GpioSpeedHigh; 78 | case MP_FLIPPER_GPIO_SPEED_VERY_HIGH: 79 | return GpioSpeedVeryHigh; 80 | default: 81 | return NO_VALUE; 82 | } 83 | } 84 | 85 | inline bool mp_flipper_gpio_init_pin( 86 | uint8_t raw_pin, 87 | uint8_t raw_mode, 88 | uint8_t raw_pull, 89 | uint8_t raw_speed) { 90 | mp_flipper_context_t* ctx = mp_flipper_context; 91 | 92 | const GpioPin* pin = decode_pin(raw_pin); 93 | const GpioMode mode = decode_mode(raw_mode); 94 | const GpioPull pull = decode_pull(raw_pull); 95 | const GpioSpeed speed = decode_speed(raw_speed); 96 | 97 | if(pin == NULL || mode == NO_VALUE || pull == NO_VALUE || speed == NO_VALUE) { 98 | return false; 99 | } 100 | 101 | if(ctx->gpio_pins[raw_pin] & MP_FLIPPER_GPIO_PIN_BLOCKED) { 102 | return false; 103 | } 104 | 105 | furi_hal_gpio_init(pin, mode, pull, speed); 106 | 107 | if(raw_mode & (MP_FLIPPER_GPIO_MODE_INTERRUPT_FALL | MP_FLIPPER_GPIO_MODE_INTERRUPT_RISE)) { 108 | furi_hal_gpio_add_int_callback(pin, mp_flipper_on_gpio, (void*)raw_pin); 109 | furi_hal_gpio_enable_int_callback(pin); 110 | } else { 111 | furi_hal_gpio_disable_int_callback(pin); 112 | furi_hal_gpio_remove_int_callback(pin); 113 | } 114 | 115 | if(raw_mode == MP_FLIPPER_GPIO_MODE_ANALOG) { 116 | ctx->adc_handle = furi_hal_adc_acquire(); 117 | 118 | furi_hal_adc_configure(ctx->adc_handle); 119 | } 120 | 121 | ctx->gpio_pins[raw_pin] = raw_mode; 122 | 123 | return true; 124 | } 125 | 126 | inline void mp_flipper_gpio_deinit_pin(uint8_t raw_pin) { 127 | const mp_flipper_context_t* ctx = mp_flipper_context; 128 | 129 | const GpioPin* pin = decode_pin(raw_pin); 130 | 131 | if(pin == NULL) { 132 | return; 133 | } 134 | 135 | if(ctx->gpio_pins[raw_pin] & (MP_FLIPPER_GPIO_PIN_BLOCKED | MP_FLIPPER_GPIO_PIN_OFF)) { 136 | return; 137 | } 138 | 139 | furi_hal_gpio_disable_int_callback(pin); 140 | furi_hal_gpio_remove_int_callback(pin); 141 | furi_hal_gpio_init_simple(pin, GpioModeAnalog); 142 | 143 | ctx->gpio_pins[raw_pin] = MP_FLIPPER_GPIO_PIN_OFF; 144 | } 145 | 146 | inline void mp_flipper_gpio_set_pin(uint8_t raw_pin, bool state) { 147 | const mp_flipper_context_t* ctx = mp_flipper_context; 148 | 149 | const GpioPin* pin = decode_pin(raw_pin); 150 | 151 | if(pin == NULL) { 152 | return; 153 | } 154 | 155 | if(ctx->gpio_pins[raw_pin] == MP_FLIPPER_GPIO_MODE_OUTPUT_PUSH_PULL || 156 | ctx->gpio_pins[raw_pin] == MP_FLIPPER_GPIO_MODE_OUTPUT_OPEN_DRAIN) { 157 | furi_hal_gpio_write(pin, state); 158 | } 159 | } 160 | 161 | inline bool mp_flipper_gpio_get_pin(uint8_t raw_pin) { 162 | const mp_flipper_context_t* ctx = mp_flipper_context; 163 | 164 | const GpioPin* pin = decode_pin(raw_pin); 165 | 166 | if(pin == NULL) { 167 | return false; 168 | } 169 | 170 | if(ctx->gpio_pins[raw_pin] == MP_FLIPPER_GPIO_MODE_INPUT) { 171 | return furi_hal_gpio_read(pin); 172 | } else { 173 | return false; 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /lib/micropython-port/mp_flipper_modflipperzero_infrared.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | 7 | #include "mp_flipper_context.h" 8 | 9 | inline static void on_rx(void* ctx, bool level, uint32_t duration) { 10 | mp_flipper_infrared_rx_t* session = ctx; 11 | 12 | if(session->pointer == 0 && !level) { 13 | return; 14 | } 15 | 16 | if(session->pointer < session->size) { 17 | session->buffer[session->pointer] = duration; 18 | 19 | session->pointer++; 20 | } else { 21 | session->running = false; 22 | } 23 | } 24 | 25 | inline static void on_rx_timeout(void* ctx) { 26 | mp_flipper_infrared_rx_t* session = ctx; 27 | 28 | session->running = false; 29 | } 30 | 31 | inline static FuriHalInfraredTxGetDataState on_tx(void* ctx, uint32_t* duration, bool* level) { 32 | mp_flipper_infrared_tx_t* session = ctx; 33 | 34 | *duration = session->provider(session->signal, session->index); 35 | *level = session->level; 36 | 37 | session->index++; 38 | 39 | if(session->index >= session->size) { 40 | session->index = 0; 41 | session->repeat--; 42 | } 43 | 44 | session->level = !session->level; 45 | 46 | return session->repeat > 0 ? FuriHalInfraredTxGetDataStateOk : 47 | FuriHalInfraredTxGetDataStateLastDone; 48 | } 49 | 50 | inline uint32_t* mp_flipper_infrared_receive(uint32_t timeout, size_t* length) { 51 | const mp_flipper_context_t* ctx = mp_flipper_context; 52 | 53 | mp_flipper_infrared_rx_t* session = ctx->infrared_rx; 54 | 55 | if(!furi_hal_infrared_is_busy()) { 56 | session->pointer = 0; 57 | session->running = true; 58 | 59 | furi_hal_infrared_async_rx_set_capture_isr_callback(on_rx, session); 60 | furi_hal_infrared_async_rx_set_timeout_isr_callback(on_rx_timeout, session); 61 | 62 | furi_hal_infrared_async_rx_start(); 63 | 64 | furi_hal_infrared_async_rx_set_timeout(timeout); 65 | 66 | while(session->running) { 67 | furi_delay_tick(10); 68 | } 69 | 70 | furi_hal_infrared_async_rx_stop(); 71 | 72 | *length = session->pointer; 73 | } else { 74 | *length = 0; 75 | } 76 | 77 | return session->buffer; 78 | } 79 | 80 | inline bool mp_flipper_infrared_transmit( 81 | void* signal, 82 | size_t length, 83 | mp_flipper_infrared_signal_tx_provider callback, 84 | uint32_t repeat, 85 | uint32_t frequency, 86 | float duty, 87 | bool use_external_pin) { 88 | if(furi_hal_infrared_is_busy() || length == 0) { 89 | return false; 90 | } 91 | 92 | const mp_flipper_context_t* ctx = mp_flipper_context; 93 | 94 | mp_flipper_infrared_tx_t* session = ctx->infrared_tx; 95 | 96 | session->index = 0; 97 | session->level = true; 98 | session->provider = callback; 99 | session->signal = signal; 100 | session->repeat = repeat; 101 | session->size = length; 102 | 103 | const FuriHalInfraredTxPin output = use_external_pin ? FuriHalInfraredTxPinExtPA7 : 104 | FuriHalInfraredTxPinInternal; 105 | 106 | furi_hal_infrared_set_tx_output(output); 107 | 108 | furi_hal_infrared_async_tx_set_data_isr_callback(on_tx, session); 109 | 110 | furi_hal_infrared_async_tx_start(frequency, duty); 111 | 112 | furi_hal_infrared_async_tx_wait_termination(); 113 | 114 | return true; 115 | } 116 | 117 | inline bool mp_flipper_infrared_is_busy() { 118 | return furi_hal_infrared_is_busy(); 119 | } 120 | -------------------------------------------------------------------------------- /lib/micropython-port/mp_flipper_modflipperzero_light.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | static Light decode_light(uint8_t value) { 6 | Light light = 0; 7 | 8 | light += value & MP_FLIPPER_LED_RED ? LightRed : 0; 9 | light += value & MP_FLIPPER_LED_GREEN ? LightGreen : 0; 10 | light += value & MP_FLIPPER_LED_BLUE ? LightBlue : 0; 11 | light += value & MP_FLIPPER_LED_BACKLIGHT ? LightBacklight : 0; 12 | 13 | return light; 14 | } 15 | 16 | inline void mp_flipper_light_set(uint8_t raw_light, uint8_t brightness) { 17 | Light light = decode_light(raw_light); 18 | 19 | furi_hal_light_set(light, brightness); 20 | } 21 | 22 | inline void mp_flipper_light_blink_start( 23 | uint8_t raw_light, 24 | uint8_t brightness, 25 | uint16_t on_time, 26 | uint16_t period) { 27 | Light light = decode_light(raw_light); 28 | 29 | furi_hal_light_blink_start(light, brightness, on_time, period); 30 | } 31 | 32 | inline void mp_flipper_light_blink_set_color(uint8_t raw_light) { 33 | Light light = decode_light(raw_light); 34 | 35 | furi_hal_light_blink_set_color(light); 36 | } 37 | 38 | inline void mp_flipper_light_blink_stop() { 39 | furi_hal_light_blink_stop(); 40 | } -------------------------------------------------------------------------------- /lib/micropython-port/mp_flipper_modflipperzero_pwm.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | 7 | #include "mp_flipper_context.h" 8 | 9 | #define NO_VALUE (-1) 10 | 11 | inline static const FuriHalPwmOutputId decode_pin_to_output_id(uint8_t pin) { 12 | switch(pin) { 13 | case MP_FLIPPER_GPIO_PIN_PA4: 14 | return FuriHalPwmOutputIdLptim2PA4; 15 | case MP_FLIPPER_GPIO_PIN_PA7: 16 | return FuriHalPwmOutputIdTim1PA7; 17 | default: 18 | return NO_VALUE; 19 | } 20 | } 21 | 22 | inline bool mp_flipper_pwm_start(uint8_t raw_pin, uint32_t frequency, uint8_t duty) { 23 | const mp_flipper_context_t* ctx = mp_flipper_context; 24 | 25 | FuriHalPwmOutputId channel = decode_pin_to_output_id(raw_pin); 26 | 27 | if(channel == NO_VALUE) { 28 | return false; 29 | } 30 | 31 | if(ctx->gpio_pins[raw_pin] != MP_FLIPPER_GPIO_PIN_OFF && 32 | ctx->gpio_pins[raw_pin] != MP_FLIPPER_GPIO_PIN_PWM) { 33 | return false; 34 | } 35 | 36 | if(ctx->gpio_pins[raw_pin] == MP_FLIPPER_GPIO_PIN_OFF) { 37 | furi_hal_pwm_start(channel, frequency, duty); 38 | } else { 39 | furi_hal_pwm_set_params(channel, frequency, duty); 40 | } 41 | 42 | ctx->gpio_pins[raw_pin] = MP_FLIPPER_GPIO_PIN_PWM; 43 | 44 | return true; 45 | } 46 | 47 | inline void mp_flipper_pwm_stop(uint8_t raw_pin) { 48 | const mp_flipper_context_t* ctx = mp_flipper_context; 49 | 50 | FuriHalPwmOutputId channel = decode_pin_to_output_id(raw_pin); 51 | 52 | if(channel == NO_VALUE) { 53 | return; 54 | } 55 | 56 | if(ctx->gpio_pins[raw_pin] != MP_FLIPPER_GPIO_PIN_PWM) { 57 | return; 58 | } 59 | 60 | furi_hal_pwm_stop(channel); 61 | 62 | ctx->gpio_pins[raw_pin] = MP_FLIPPER_GPIO_PIN_OFF; 63 | } 64 | 65 | inline bool mp_flipper_pwm_is_running(uint8_t raw_pin) { 66 | const mp_flipper_context_t* ctx = mp_flipper_context; 67 | 68 | FuriHalPwmOutputId channel = decode_pin_to_output_id(raw_pin); 69 | 70 | if(channel == NO_VALUE) { 71 | return false; 72 | } 73 | 74 | if(ctx->gpio_pins[raw_pin] != MP_FLIPPER_GPIO_PIN_PWM) { 75 | return false; 76 | } 77 | 78 | return furi_hal_pwm_is_running(channel); 79 | } 80 | -------------------------------------------------------------------------------- /lib/micropython-port/mp_flipper_modflipperzero_speaker.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | inline bool mp_flipper_speaker_start(float frequency, float volume) { 6 | if(furi_hal_speaker_acquire(100)) { 7 | furi_hal_speaker_start(frequency, volume); 8 | 9 | return true; 10 | } 11 | 12 | return false; 13 | } 14 | 15 | inline bool mp_flipper_speaker_set_volume(float volume) { 16 | if(furi_hal_speaker_is_mine()) { 17 | furi_hal_speaker_set_volume(volume); 18 | 19 | return true; 20 | } 21 | 22 | return false; 23 | } 24 | 25 | inline bool mp_flipper_speaker_stop() { 26 | if(furi_hal_speaker_is_mine()) { 27 | furi_hal_speaker_stop(); 28 | 29 | furi_hal_speaker_release(); 30 | 31 | return true; 32 | } 33 | 34 | return false; 35 | } 36 | -------------------------------------------------------------------------------- /lib/micropython-port/mp_flipper_modflipperzero_uart.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | 6 | typedef struct { 7 | FuriHalSerialHandle* handle; 8 | FuriStreamBuffer* rx_buffer; 9 | } mp_flipper_uart_ctx_t; 10 | 11 | static void on_uart_rx(FuriHalSerialHandle* handle, FuriHalSerialRxEvent event, void* context) { 12 | FuriStreamBuffer* buffer = context; 13 | 14 | if(event & FuriHalSerialRxEventData) { 15 | uint8_t data = furi_hal_serial_async_rx(handle); 16 | furi_stream_buffer_send(buffer, &data, 1, 0); 17 | } 18 | } 19 | 20 | inline void* mp_flipper_uart_open(uint8_t raw_mode, uint32_t baud_rate) { 21 | FuriHalSerialId mode = raw_mode == MP_FLIPPER_UART_MODE_USART ? FuriHalSerialIdUsart : 22 | FuriHalSerialIdLpuart; 23 | 24 | if(furi_hal_serial_control_is_busy(mode)) { 25 | return NULL; 26 | } 27 | 28 | mp_flipper_uart_ctx_t* ctx = malloc(sizeof(mp_flipper_uart_ctx_t)); 29 | 30 | ctx->handle = furi_hal_serial_control_acquire(mode); 31 | ctx->rx_buffer = furi_stream_buffer_alloc(512, 1); 32 | 33 | furi_hal_serial_init(ctx->handle, baud_rate); 34 | 35 | furi_hal_serial_async_rx_start(ctx->handle, on_uart_rx, ctx->rx_buffer, false); 36 | 37 | return ctx; 38 | } 39 | 40 | inline bool mp_flipper_uart_close(void* handle) { 41 | mp_flipper_uart_ctx_t* ctx = handle; 42 | 43 | furi_hal_serial_deinit(ctx->handle); 44 | furi_hal_serial_control_release(ctx->handle); 45 | 46 | furi_stream_buffer_free(ctx->rx_buffer); 47 | 48 | free(ctx); 49 | 50 | return true; 51 | } 52 | 53 | inline bool mp_flipper_uart_sync(void* handle) { 54 | mp_flipper_uart_ctx_t* ctx = handle; 55 | 56 | furi_hal_serial_tx_wait_complete(ctx->handle); 57 | } 58 | 59 | inline size_t mp_flipper_uart_read(void* handle, void* buffer, size_t size, int* errcode) { 60 | mp_flipper_uart_ctx_t* ctx = handle; 61 | 62 | size_t read = 0; 63 | size_t total = 0; 64 | size_t left = size; 65 | 66 | do { 67 | read = furi_stream_buffer_receive(ctx->rx_buffer, &buffer[read], left, 0); 68 | total += read; 69 | left -= read; 70 | } while(read > 0); 71 | 72 | return total; 73 | } 74 | 75 | inline size_t mp_flipper_uart_write(void* handle, const void* buffer, size_t size, int* errcode) { 76 | mp_flipper_uart_ctx_t* ctx = handle; 77 | 78 | furi_hal_serial_tx(ctx->handle, buffer, size); 79 | 80 | return size; 81 | } 82 | -------------------------------------------------------------------------------- /lib/micropython-port/mp_flipper_modflipperzero_vibro.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | inline void mp_flipper_vibro(bool state) { 6 | furi_hal_vibro_on(state); 7 | } 8 | -------------------------------------------------------------------------------- /lib/micropython-port/mp_flipper_modrandom.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | inline uint32_t mp_flipper_seed_init() { 6 | furi_hal_random_init(); 7 | 8 | return furi_hal_random_get(); 9 | } 10 | -------------------------------------------------------------------------------- /lib/micropython-port/mp_flipper_modtime.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | 6 | inline uint32_t mp_flipper_get_timestamp() { 7 | return furi_hal_rtc_get_timestamp(); 8 | } 9 | 10 | inline uint32_t mp_flipper_get_tick_frequency() { 11 | return furi_kernel_get_tick_frequency(); 12 | } 13 | 14 | inline uint32_t mp_flipper_get_tick() { 15 | return furi_get_tick(); 16 | } 17 | 18 | inline void mp_flipper_delay_ms(uint32_t ms) { 19 | furi_delay_ms(ms); 20 | } 21 | 22 | inline void mp_flipper_delay_us(uint32_t us) { 23 | furi_delay_us(us); 24 | } 25 | -------------------------------------------------------------------------------- /lib/micropython-port/mp_flipper_polyfill.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #define POLYFILL_FUN_1(name, ret, arg1) ret name(arg1) 8 | #define POLYFILL_FUN_2(name, ret, arg1, arg2) ret name(arg1, arg2) 9 | #define POLYFILL_FUN_3(name, ret, arg1, arg2, arg3) ret name(arg1, arg2, arg3) 10 | #define POLYFILL_FUN_4(name, ret, arg1, arg2, arg3, arg4) ret name(arg1, arg2, arg3, arg4) 11 | 12 | #ifndef __aeabi_l2f 13 | POLYFILL_FUN_1(__aeabi_l2f, float, long long x) { 14 | return x; 15 | } 16 | #endif 17 | 18 | #ifndef __aeabi_f2lz 19 | POLYFILL_FUN_1(__aeabi_f2lz, long long, float x) { 20 | return x; 21 | } 22 | #endif 23 | 24 | #ifndef __aeabi_dcmple 25 | POLYFILL_FUN_2(__aeabi_dcmple, double, double, double) { 26 | } 27 | #endif 28 | 29 | #ifndef __aeabi_dcmplt 30 | POLYFILL_FUN_2(__aeabi_dcmplt, double, double, double) { 31 | } 32 | #endif 33 | 34 | #ifndef __aeabi_dcmpun 35 | POLYFILL_FUN_2(__aeabi_dcmpun, double, double, double) { 36 | } 37 | #endif 38 | 39 | #ifndef __aeabi_dcmpeq 40 | POLYFILL_FUN_2(__aeabi_dcmpeq, double, double, double) { 41 | } 42 | #endif 43 | 44 | #ifndef __aeabi_dmul 45 | double __aeabi_dmul(double x, double y) { 46 | return x * y; 47 | } 48 | #endif 49 | 50 | #ifndef __aeabi_dadd 51 | POLYFILL_FUN_2(__aeabi_dadd, double, double x, double y) { 52 | return x + y; 53 | } 54 | #endif 55 | 56 | #ifndef __aeabi_ddiv 57 | POLYFILL_FUN_2(__aeabi_ddiv, double, double x, double y) { 58 | return x / y; 59 | } 60 | #endif 61 | 62 | #ifndef __aeabi_l2d 63 | POLYFILL_FUN_1(__aeabi_l2d, double, long x) { 64 | return x; 65 | } 66 | #endif 67 | 68 | #ifndef __aeabi_f2d 69 | POLYFILL_FUN_1(__aeabi_f2d, double, float x) { 70 | return x; 71 | } 72 | #endif 73 | 74 | #ifndef __aeabi_dsub 75 | POLYFILL_FUN_2(__aeabi_dsub, double, double x, double y) { 76 | return x - y; 77 | } 78 | #endif 79 | 80 | #ifndef __aeabi_dcmpge 81 | POLYFILL_FUN_2(__aeabi_dcmpge, double, double, double) { 82 | } 83 | #endif 84 | 85 | #ifndef __aeabi_i2d 86 | POLYFILL_FUN_1(__aeabi_i2d, double, int x) { 87 | return x; 88 | } 89 | #endif 90 | 91 | #ifndef __aeabi_dcmpgt 92 | POLYFILL_FUN_1(__aeabi_dcmpgt, double, double) { 93 | } 94 | #endif 95 | 96 | #ifndef __aeabi_d2iz 97 | POLYFILL_FUN_1(__aeabi_d2iz, long int, double x) { 98 | return x; 99 | } 100 | #endif 101 | 102 | #ifndef __aeabi_d2lz 103 | POLYFILL_FUN_1(__aeabi_d2lz, long, double x) { 104 | return x; 105 | } 106 | #endif 107 | 108 | #ifndef __aeabi_d2uiz 109 | POLYFILL_FUN_1(__aeabi_d2uiz, unsigned long int, double x) { 110 | return x; 111 | } 112 | #endif 113 | 114 | #ifndef __aeabi_d2f 115 | POLYFILL_FUN_1(__aeabi_d2f, float, double x) { 116 | return x; 117 | } 118 | #endif 119 | 120 | #ifndef __aeabi_ldivmod 121 | POLYFILL_FUN_2(__aeabi_ldivmod, long, long, long) { 122 | } 123 | #endif 124 | 125 | #ifndef strtox 126 | POLYFILL_FUN_4(strtox, long long unsigned int, const char*, char**, int, long long unsigned int) { 127 | } 128 | #endif 129 | 130 | #if FLT_EVAL_METHOD == 0 || FLT_EVAL_METHOD == 1 131 | #define EPS DBL_EPSILON 132 | #elif FLT_EVAL_METHOD == 2 133 | #define EPS LDBL_EPSILON 134 | #endif 135 | static const double_t toint = 1 / EPS; 136 | 137 | #ifndef FORCE_EVAL 138 | #define FORCE_EVAL(x) \ 139 | do { \ 140 | if(sizeof(x) == sizeof(float)) { \ 141 | volatile float __x; \ 142 | __x = (x); \ 143 | (void)__x; \ 144 | } else if(sizeof(x) == sizeof(double)) { \ 145 | volatile double __x; \ 146 | __x = (x); \ 147 | (void)__x; \ 148 | } else { \ 149 | volatile long double __x; \ 150 | __x = (x); \ 151 | (void)__x; \ 152 | } \ 153 | } while(0) 154 | #endif 155 | 156 | #ifndef floorf 157 | float floorf(float x) { 158 | union { 159 | float f; 160 | uint32_t i; 161 | } u = {x}; 162 | int e = (int)(u.i >> 23 & 0xff) - 0x7f; 163 | uint32_t m; 164 | 165 | if(e >= 23) return x; 166 | if(e >= 0) { 167 | m = 0x007fffff >> e; 168 | if((u.i & m) == 0) return x; 169 | FORCE_EVAL(x + 0x1p120f); 170 | if(u.i >> 31) u.i += m; 171 | u.i &= ~m; 172 | } else { 173 | FORCE_EVAL(x + 0x1p120f); 174 | if(u.i >> 31 == 0) 175 | u.i = 0; 176 | else if(u.i << 1) 177 | u.f = -1.0; 178 | } 179 | return u.f; 180 | } 181 | #endif 182 | 183 | #ifndef floor 184 | double floor(double x) { 185 | union { 186 | double f; 187 | uint64_t i; 188 | } u = {x}; 189 | int e = u.i >> 52 & 0x7ff; 190 | double_t y; 191 | 192 | if(e >= 0x3ff + 52 || x == 0) return x; 193 | /* y = int(x) - x, where int(x) is an integer neighbor of x */ 194 | if(u.i >> 63) 195 | y = x - toint + toint - x; 196 | else 197 | y = x + toint - toint - x; 198 | /* special case because of non-nearest rounding modes */ 199 | if(e <= 0x3ff - 1) { 200 | FORCE_EVAL(y); 201 | return u.i >> 63 ? -1 : 0; 202 | } 203 | if(y > 0) return x + y - 1; 204 | return x + y; 205 | } 206 | #endif 207 | 208 | #ifndef fmodf 209 | float fmodf(float x, float y) { 210 | union { 211 | float f; 212 | uint32_t i; 213 | } ux = {x}, uy = {y}; 214 | int ex = ux.i >> 23 & 0xff; 215 | int ey = uy.i >> 23 & 0xff; 216 | uint32_t sx = ux.i & 0x80000000; 217 | uint32_t i; 218 | uint32_t uxi = ux.i; 219 | 220 | if(uy.i << 1 == 0 || isnan(y) || ex == 0xff) return (x * y) / (x * y); 221 | if(uxi << 1 <= uy.i << 1) { 222 | if(uxi << 1 == uy.i << 1) return 0 * x; 223 | return x; 224 | } 225 | 226 | /* normalize x and y */ 227 | if(!ex) { 228 | for(i = uxi << 9; i >> 31 == 0; ex--, i <<= 1) 229 | ; 230 | uxi <<= -ex + 1; 231 | } else { 232 | uxi &= -1U >> 9; 233 | uxi |= 1U << 23; 234 | } 235 | if(!ey) { 236 | for(i = uy.i << 9; i >> 31 == 0; ey--, i <<= 1) 237 | ; 238 | uy.i <<= -ey + 1; 239 | } else { 240 | uy.i &= -1U >> 9; 241 | uy.i |= 1U << 23; 242 | } 243 | 244 | /* x mod y */ 245 | for(; ex > ey; ex--) { 246 | i = uxi - uy.i; 247 | if(i >> 31 == 0) { 248 | if(i == 0) return 0 * x; 249 | uxi = i; 250 | } 251 | uxi <<= 1; 252 | } 253 | i = uxi - uy.i; 254 | if(i >> 31 == 0) { 255 | if(i == 0) return 0 * x; 256 | uxi = i; 257 | } 258 | for(; uxi >> 23 == 0; uxi <<= 1, ex--) 259 | ; 260 | 261 | /* scale result up */ 262 | if(ex > 0) { 263 | uxi -= 1U << 23; 264 | uxi |= (uint32_t)ex << 23; 265 | } else { 266 | uxi >>= -ex + 1; 267 | } 268 | uxi |= sx; 269 | ux.i = uxi; 270 | return ux.f; 271 | } 272 | #endif 273 | 274 | #ifndef fmod 275 | double fmod(double x, double y) { 276 | union { 277 | double f; 278 | uint64_t i; 279 | } ux = {x}, uy = {y}; 280 | int ex = ux.i >> 52 & 0x7ff; 281 | int ey = uy.i >> 52 & 0x7ff; 282 | int sx = ux.i >> 63; 283 | uint64_t i; 284 | 285 | /* in the followings uxi should be ux.i, but then gcc wrongly adds */ 286 | /* float load/store to inner loops ruining performance and code size */ 287 | uint64_t uxi = ux.i; 288 | 289 | if(uy.i << 1 == 0 || isnan(y) || ex == 0x7ff) return (x * y) / (x * y); 290 | if(uxi << 1 <= uy.i << 1) { 291 | if(uxi << 1 == uy.i << 1) return 0 * x; 292 | return x; 293 | } 294 | 295 | /* normalize x and y */ 296 | if(!ex) { 297 | for(i = uxi << 12; i >> 63 == 0; ex--, i <<= 1) 298 | ; 299 | uxi <<= -ex + 1; 300 | } else { 301 | uxi &= -1ULL >> 12; 302 | uxi |= 1ULL << 52; 303 | } 304 | if(!ey) { 305 | for(i = uy.i << 12; i >> 63 == 0; ey--, i <<= 1) 306 | ; 307 | uy.i <<= -ey + 1; 308 | } else { 309 | uy.i &= -1ULL >> 12; 310 | uy.i |= 1ULL << 52; 311 | } 312 | 313 | /* x mod y */ 314 | for(; ex > ey; ex--) { 315 | i = uxi - uy.i; 316 | if(i >> 63 == 0) { 317 | if(i == 0) return 0 * x; 318 | uxi = i; 319 | } 320 | uxi <<= 1; 321 | } 322 | i = uxi - uy.i; 323 | if(i >> 63 == 0) { 324 | if(i == 0) return 0 * x; 325 | uxi = i; 326 | } 327 | for(; uxi >> 52 == 0; uxi <<= 1, ex--) 328 | ; 329 | 330 | /* scale result */ 331 | if(ex > 0) { 332 | uxi -= 1ULL << 52; 333 | uxi |= (uint64_t)ex << 52; 334 | } else { 335 | uxi >>= -ex + 1; 336 | } 337 | uxi |= (uint64_t)sx << 63; 338 | ux.i = uxi; 339 | return ux.f; 340 | } 341 | #endif 342 | 343 | #ifndef nearbyintf 344 | float nearbyintf(float x) { 345 | union { 346 | float f; 347 | uint32_t i; 348 | } u = {x}; 349 | int e = u.i >> 23 & 0xff; 350 | int s = u.i >> 31; 351 | float_t y; 352 | 353 | if(e >= 0x7f + 23) return x; 354 | if(s) 355 | y = x - 0x1p23f + 0x1p23f; 356 | else 357 | y = x + 0x1p23f - 0x1p23f; 358 | if(y == 0) return s ? -0.0f : 0.0f; 359 | return y; 360 | } 361 | #endif 362 | 363 | #ifndef stroll 364 | long long strtoll(const char* restrict s, char** restrict p, int base) { 365 | return strtox(s, p, base, LLONG_MIN); 366 | } 367 | #endif 368 | 369 | #ifndef pow 370 | double pow(double x, double y) { 371 | return powf(x, y); 372 | } 373 | #endif 374 | 375 | #ifndef rint 376 | 377 | double rint(double x) { 378 | union { 379 | double f; 380 | uint64_t i; 381 | } u = {x}; 382 | int e = u.i >> 52 & 0x7ff; 383 | int s = u.i >> 63; 384 | double_t y; 385 | 386 | if(e >= 0x3ff + 52) return x; 387 | if(s) 388 | y = x - toint + toint; 389 | else 390 | y = x + toint - toint; 391 | if(y == 0) return s ? -0.0 : 0; 392 | return y; 393 | } 394 | #endif 395 | 396 | #ifndef nearbyint 397 | double nearbyint(double x) { 398 | #ifdef FE_INEXACT 399 | #pragma STDC FENV_ACCESS ON 400 | int e; 401 | 402 | e = fetestexcept(FE_INEXACT); 403 | #endif 404 | x = rint(x); 405 | #ifdef FE_INEXACT 406 | if(!e) feclearexcept(FE_INEXACT); 407 | #endif 408 | return x; 409 | } 410 | #endif 411 | -------------------------------------------------------------------------------- /lib/micropython-port/mp_flipper_runtime.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | #include "mp_flipper_context.h" 10 | 11 | static void on_input_callback(const InputEvent* event, void* ctx) { 12 | uint16_t button = 1 << event->key; 13 | uint16_t type = 1 << (InputKeyMAX + event->type); 14 | 15 | mp_flipper_on_input(button, type); 16 | } 17 | 18 | void mp_flipper_save_file(const char* file_path, const char* data, size_t size) { 19 | mp_flipper_context_t* ctx = mp_flipper_context; 20 | 21 | File* file = storage_file_alloc(ctx->storage); 22 | 23 | do { 24 | if(!storage_file_open(file, file_path, FSAM_WRITE, FSOM_CREATE_ALWAYS)) { 25 | storage_file_free(file); 26 | 27 | mp_flipper_raise_os_error_with_filename(MP_ENOENT, file_path); 28 | 29 | break; 30 | } 31 | 32 | storage_file_write(file, data, size); 33 | } while(false); 34 | 35 | storage_file_free(file); 36 | } 37 | 38 | inline void mp_flipper_nlr_jump_fail(void* val) { 39 | furi_crash(); 40 | } 41 | 42 | inline void mp_flipper_assert(const char* file, int line, const char* func, const char* expr) { 43 | } 44 | 45 | inline void mp_flipper_fatal_error(const char* msg) { 46 | furi_crash(msg); 47 | } 48 | 49 | const char* mp_flipper_print_get_data(void* data) { 50 | return furi_string_get_cstr(data); 51 | } 52 | 53 | size_t mp_flipper_print_get_data_length(void* data) { 54 | return furi_string_size(data); 55 | } 56 | 57 | void* mp_flipper_print_data_alloc() { 58 | return furi_string_alloc(); 59 | } 60 | 61 | void mp_flipper_print_strn(void* data, const char* str, size_t length) { 62 | for(size_t i = 0; i < length; i++) { 63 | furi_string_push_back(data, str[i]); 64 | } 65 | } 66 | 67 | void mp_flipper_print_data_free(void* data) { 68 | furi_string_free(data); 69 | } 70 | 71 | void* mp_flipper_context_alloc() { 72 | mp_flipper_context_t* ctx = malloc(sizeof(mp_flipper_context_t)); 73 | 74 | ctx->gui = furi_record_open(RECORD_GUI); 75 | ctx->view_port = view_port_alloc(); 76 | 77 | ctx->input_event_queue = furi_record_open(RECORD_INPUT_EVENTS); 78 | ctx->input_event = furi_pubsub_subscribe(ctx->input_event_queue, on_input_callback, NULL); 79 | 80 | gui_add_view_port(ctx->gui, ctx->view_port, GuiLayerFullscreen); 81 | 82 | ctx->canvas = gui_direct_draw_acquire(ctx->gui); 83 | 84 | ctx->dialog_message = dialog_message_alloc(); 85 | ctx->dialog_message_button_left = NULL; 86 | ctx->dialog_message_button_center = NULL; 87 | ctx->dialog_message_button_right = NULL; 88 | 89 | ctx->storage = furi_record_open(RECORD_STORAGE); 90 | 91 | ctx->adc_handle = NULL; 92 | 93 | // GPIO 94 | ctx->gpio_pins = malloc(MP_FLIPPER_GPIO_PINS * sizeof(mp_flipper_gpio_pin_t)); 95 | 96 | for(uint8_t pin = 0; pin < MP_FLIPPER_GPIO_PINS; pin++) { 97 | ctx->gpio_pins[pin] = MP_FLIPPER_GPIO_PIN_OFF; 98 | } 99 | 100 | // infrared rx 101 | ctx->infrared_rx = malloc(sizeof(mp_flipper_infrared_rx_t)); 102 | 103 | ctx->infrared_rx->size = MP_FLIPPER_INFRARED_RX_BUFFER_SIZE; 104 | ctx->infrared_rx->buffer = calloc(ctx->infrared_rx->size, sizeof(uint16_t)); 105 | ctx->infrared_rx->pointer = 0; 106 | ctx->infrared_rx->running = true; 107 | 108 | // infrared tx 109 | ctx->infrared_tx = malloc(sizeof(mp_flipper_infrared_tx_t)); 110 | ctx->infrared_tx->index = 0; 111 | ctx->infrared_tx->provider = NULL; 112 | ctx->infrared_tx->repeat = 0; 113 | ctx->infrared_tx->signal = NULL; 114 | ctx->infrared_tx->size = 0; 115 | ctx->infrared_tx->level = false; 116 | 117 | return ctx; 118 | } 119 | 120 | void mp_flipper_context_free(void* context) { 121 | mp_flipper_context_t* ctx = context; 122 | 123 | gui_direct_draw_release(ctx->gui); 124 | 125 | furi_pubsub_unsubscribe(ctx->input_event_queue, ctx->input_event); 126 | 127 | gui_remove_view_port(ctx->gui, ctx->view_port); 128 | 129 | view_port_free(ctx->view_port); 130 | 131 | dialog_message_free(ctx->dialog_message); 132 | 133 | furi_record_close(RECORD_GUI); 134 | furi_record_close(RECORD_INPUT_EVENTS); 135 | 136 | furi_record_close(RECORD_STORAGE); 137 | 138 | // disable ADC handle 139 | if(ctx->adc_handle) { 140 | furi_hal_adc_release(ctx->adc_handle); 141 | } 142 | 143 | // de-initialize all GPIO pins 144 | for(uint8_t pin = 0; pin < MP_FLIPPER_GPIO_PINS; pin++) { 145 | mp_flipper_gpio_deinit_pin(pin); 146 | } 147 | 148 | // stop running PWM output 149 | mp_flipper_pwm_stop(MP_FLIPPER_GPIO_PIN_PA4); 150 | mp_flipper_pwm_stop(MP_FLIPPER_GPIO_PIN_PA7); 151 | 152 | free(ctx->gpio_pins); 153 | 154 | // stop infrared 155 | free(ctx->infrared_rx->buffer); 156 | free(ctx->infrared_rx); 157 | free(ctx->infrared_tx); 158 | 159 | free(ctx); 160 | } 161 | -------------------------------------------------------------------------------- /logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 16 | 18 | 38 | 45 | 52 | 59 | 66 | 73 | 80 | 87 | 94 | 99 | 104 | 105 | 110 | 111 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "terminalizer": "^0.12.0" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /publish.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | readonly BRANCH="${1}" 4 | readonly TARGET='./temp' 5 | readonly REMOTE='git@github.com:ofabel/mp-flipper.git' 6 | 7 | set -e 8 | 9 | rm -rf ${TARGET} 10 | 11 | git init -b ${BRANCH} ${TARGET} && cd ${TARGET} && git remote add origin ${REMOTE} && cd .. 12 | 13 | rm -rf ${TARGET}/* 14 | 15 | cp -r dist/pages/* ${TARGET} 16 | touch ${TARGET}/.nojekyll 17 | 18 | cd ${TARGET} 19 | 20 | git add . && git commit -m "update docs" && git push origin ${BRANCH} --force || cd . 21 | 22 | cd .. 23 | 24 | rm -rf ${TARGET} 25 | 26 | exit 0 27 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["hatchling"] 3 | build-backend = "hatchling.build" 4 | 5 | [project] 6 | name = "flipperzero" 7 | version = "1.8.0" 8 | authors = [ 9 | { name = "Oliver Fabel" }, 10 | ] 11 | description = "Python support for Flipper Zero." 12 | readme = "flipperzero/README.md" 13 | license = "MIT" 14 | requires-python = ">=3.8" 15 | classifiers = [ 16 | "Programming Language :: Python :: Implementation :: MicroPython", 17 | "Development Status :: 5 - Production/Stable", 18 | "License :: OSI Approved :: MIT License", 19 | "Operating System :: Other OS", 20 | ] 21 | keywords = [ 22 | "flipperzero", 23 | "Flipper Zero", 24 | "MicroPython", 25 | "f0", 26 | "GPIO", 27 | "PWM", 28 | "ADC", 29 | "Infrared" 30 | ] 31 | 32 | [project.urls] 33 | "Documentation" = "https://ofabel.github.io/mp-flipper/" 34 | "Source code" = "https://github.com/ofabel/mp-flipper" 35 | "Issue Tracker" = "https://github.com/ofabel/mp-flipper/issues" 36 | "Changelog" = "https://github.com/ofabel/mp-flipper/blob/master/CHANGELOG.md" 37 | 38 | [tool.hatch.build] 39 | directory = "dist/python" 40 | include = [ 41 | "flipperzero/__init__.py" 42 | ] 43 | ignore-vcs = true 44 | only-packages = true 45 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Sphinx==8.0.2 2 | myst-parser==4.0.0 3 | hatch==1.12.0 -------------------------------------------------------------------------------- /splash.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 39 | 46 | 53 | 60 | 67 | 74 | 81 | 88 | 95 | 102 | 109 | 114 | 119 | 126 | 133 | 140 | 147 | 148 | 150 | 154 | 158 | 165 | 166 | 167 | -------------------------------------------------------------------------------- /upython.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | 7 | #include "upython.h" 8 | 9 | volatile Action action = ActionNone; 10 | FuriString* file_path = NULL; 11 | volatile FuriThreadStdoutWriteCallback stdout_callback = NULL; 12 | 13 | static void write_to_log_output(const char* data, size_t size, void* context) { 14 | UNUSED(context); 15 | 16 | furi_log_tx((const uint8_t*)data, size); 17 | } 18 | 19 | void upython_reset_file_path() { 20 | furi_string_set(file_path, APP_ASSETS_PATH("upython")); 21 | } 22 | 23 | int32_t upython(void* args) { 24 | upython_cli_register(args); 25 | 26 | do { 27 | switch(action) { 28 | case ActionNone: 29 | action = upython_splash_screen(); 30 | 31 | break; 32 | case ActionOpen: 33 | if(upython_select_python_file(file_path)) { 34 | stdout_callback = write_to_log_output; 35 | action = ActionExec; 36 | } else { 37 | upython_reset_file_path(); 38 | 39 | action = ActionNone; 40 | } 41 | 42 | break; 43 | case ActionRepl: 44 | break; 45 | case ActionExec: 46 | furi_thread_set_stdout_callback(stdout_callback, NULL); 47 | 48 | upython_file_execute(file_path); 49 | 50 | upython_reset_file_path(); 51 | 52 | action = ActionNone; 53 | 54 | furi_thread_set_stdout_callback(stdout_callback = NULL, NULL); 55 | 56 | break; 57 | case ActionExit: 58 | action = upython_confirm_exit_action() ? ActionTerm : ActionNone; 59 | break; 60 | case ActionTerm: 61 | break; 62 | } 63 | 64 | furi_delay_ms(1); 65 | } while(action != ActionTerm); 66 | 67 | upython_cli_unregister(args); 68 | 69 | return 0; 70 | } 71 | -------------------------------------------------------------------------------- /upython.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #define TAG "uPython" 7 | #define CLI "py" 8 | 9 | typedef enum { 10 | ActionNone, 11 | ActionOpen, 12 | ActionRepl, 13 | ActionExec, 14 | ActionExit, 15 | ActionTerm 16 | } Action; 17 | 18 | extern FuriString* file_path; 19 | extern volatile Action action; 20 | extern volatile FuriThreadStdoutWriteCallback stdout_callback; 21 | 22 | void upython_reset_file_path(); 23 | 24 | Action upython_splash_screen(); 25 | bool upython_confirm_exit_action(); 26 | bool upython_select_python_file(FuriString* file_path); 27 | 28 | void upython_cli_register(void* args); 29 | void upython_cli_unregister(void* args); 30 | 31 | void upython_cli(PipeSide* pipe, FuriString* args, void* ctx); 32 | 33 | void upython_repl_execute(); 34 | 35 | void upython_file_execute(FuriString* file); 36 | -------------------------------------------------------------------------------- /upython_cli.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "upython.h" 6 | 7 | static FuriStreamBuffer* stdout_buffer = NULL; 8 | 9 | static void write_to_stdout_buffer(const char* data, size_t size, void* context) { 10 | UNUSED(context); 11 | 12 | furi_stream_buffer_send(stdout_buffer, data, size, 0); 13 | } 14 | 15 | void upython_cli(PipeSide* pipe, FuriString* args, void* ctx) { 16 | UNUSED(ctx); 17 | 18 | pipe_install_as_stdio(pipe); 19 | 20 | if(action != ActionNone) { 21 | printf("%s is busy!\n", TAG); 22 | 23 | return; 24 | } 25 | 26 | if(furi_string_empty(args)) { 27 | action = ActionRepl; 28 | 29 | upython_repl_execute(); 30 | 31 | action = ActionNone; 32 | } else { 33 | furi_string_set(file_path, args); 34 | 35 | stdout_buffer = furi_stream_buffer_alloc(128, 1); 36 | 37 | stdout_callback = write_to_stdout_buffer; 38 | 39 | action = ActionExec; 40 | 41 | char data = '\0'; 42 | 43 | while(action == ActionExec || !furi_stream_buffer_is_empty(stdout_buffer)) { 44 | if(furi_stream_buffer_receive(stdout_buffer, &data, 1, 0) > 0) { 45 | printf("%c", data); 46 | } 47 | } 48 | 49 | furi_stream_buffer_free(stdout_buffer); 50 | } 51 | } 52 | 53 | void upython_cli_register(void* args) { 54 | if(args != NULL) { 55 | file_path = furi_string_alloc_set_str(args); 56 | 57 | action = ActionExec; 58 | 59 | return; 60 | } else { 61 | file_path = furi_string_alloc(); 62 | 63 | upython_reset_file_path(); 64 | 65 | action = ActionNone; 66 | } 67 | 68 | CliRegistry* cli = furi_record_open(RECORD_CLI); 69 | 70 | cli_registry_add_command(cli, CLI, CliCommandFlagParallelSafe, upython_cli, NULL); 71 | 72 | furi_record_close(RECORD_CLI); 73 | } 74 | 75 | void upython_cli_unregister(void* args) { 76 | furi_string_free(file_path); 77 | 78 | if(args != NULL) { 79 | return; 80 | } 81 | 82 | CliRegistry* cli = furi_record_open(RECORD_CLI); 83 | 84 | cli_registry_delete_command(cli, CLI); 85 | 86 | furi_record_close(RECORD_CLI); 87 | } 88 | -------------------------------------------------------------------------------- /upython_file.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | #include "upython.h" 10 | 11 | void upython_file_execute(FuriString* file) { 12 | size_t stack; 13 | 14 | const char* path = furi_string_get_cstr(file); 15 | FuriString* file_path = furi_string_alloc_printf("%s", path); 16 | 17 | do { 18 | FURI_LOG_I(TAG, "executing script %s", path); 19 | 20 | const size_t heap_size = memmgr_get_free_heap() * 0.1; 21 | const size_t stack_size = 2 * 1024; 22 | uint8_t* heap = malloc(heap_size * sizeof(uint8_t)); 23 | 24 | FURI_LOG_D(TAG, "initial heap size is %zu bytes", heap_size); 25 | FURI_LOG_D(TAG, "stack size is %zu bytes", stack_size); 26 | 27 | size_t index = furi_string_search_rchar(file_path, '/'); 28 | 29 | if(index == FURI_STRING_FAILURE) { 30 | FURI_LOG_E(TAG, "invalid file path"); 31 | 32 | break; 33 | } 34 | 35 | bool is_py_file = furi_string_end_with_str(file_path, ".py"); 36 | 37 | furi_string_left(file_path, index); 38 | 39 | mp_flipper_set_root_module_path(furi_string_get_cstr(file_path)); 40 | 41 | mp_flipper_init(heap, heap_size, stack_size, &stack); 42 | 43 | if(is_py_file) { 44 | mp_flipper_exec_py_file(path); 45 | } 46 | 47 | mp_flipper_deinit(); 48 | 49 | free(heap); 50 | } while(false); 51 | 52 | furi_string_free(file_path); 53 | } 54 | -------------------------------------------------------------------------------- /upython_repl.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | #include 10 | 11 | #include "upython.h" 12 | 13 | #define AUTOCOMPLETE_MANY_MATCHES (size_t)(-1) 14 | #define HISTORY_SIZE 16 15 | 16 | typedef enum { 17 | CliSymbolAsciiSOH = 0x01, 18 | CliSymbolAsciiETX = 0x03, 19 | CliSymbolAsciiEOT = 0x04, 20 | CliSymbolAsciiBell = 0x07, 21 | CliSymbolAsciiBackspace = 0x08, 22 | CliSymbolAsciiTab = 0x09, 23 | CliSymbolAsciiLF = 0x0A, 24 | CliSymbolAsciiCR = 0x0D, 25 | CliSymbolAsciiEsc = 0x1B, 26 | CliSymbolAsciiUS = 0x1F, 27 | CliSymbolAsciiSpace = 0x20, 28 | CliSymbolAsciiDel = 0x7F, 29 | } CliSymbols; 30 | 31 | typedef struct { 32 | FuriString** stack; 33 | size_t pointer; 34 | size_t size; 35 | } mp_flipper_repl_history_t; 36 | 37 | typedef struct { 38 | mp_flipper_repl_history_t* history; 39 | FuriString* line; 40 | FuriString* code; 41 | size_t cursor; 42 | bool is_ps2; 43 | } mp_flipper_repl_context_t; 44 | 45 | static mp_flipper_repl_history_t* mp_flipper_repl_history_alloc() { 46 | mp_flipper_repl_history_t* history = malloc(sizeof(mp_flipper_repl_history_t)); 47 | 48 | history->stack = malloc(HISTORY_SIZE * sizeof(FuriString*)); 49 | history->pointer = 0; 50 | history->size = 1; 51 | 52 | for(size_t i = 0; i < HISTORY_SIZE; i++) { 53 | history->stack[i] = furi_string_alloc(); 54 | } 55 | 56 | return history; 57 | } 58 | 59 | static void mp_flipper_repl_history_free(mp_flipper_repl_history_t* history) { 60 | for(size_t i = 0; i < HISTORY_SIZE; i++) { 61 | furi_string_free(history->stack[i]); 62 | } 63 | 64 | free(history); 65 | } 66 | 67 | static mp_flipper_repl_context_t* mp_flipper_repl_context_alloc() { 68 | mp_flipper_repl_context_t* context = malloc(sizeof(mp_flipper_repl_context_t)); 69 | 70 | context->history = mp_flipper_repl_history_alloc(); 71 | context->code = furi_string_alloc(); 72 | context->line = furi_string_alloc(); 73 | context->cursor = 0; 74 | context->is_ps2 = false; 75 | 76 | return context; 77 | } 78 | 79 | static void mp_flipper_repl_context_free(mp_flipper_repl_context_t* context) { 80 | mp_flipper_repl_history_free(context->history); 81 | 82 | furi_string_free(context->code); 83 | furi_string_free(context->line); 84 | 85 | free(context); 86 | } 87 | 88 | static void print_full_psx(mp_flipper_repl_context_t* context) { 89 | const char* psx = context->is_ps2 ? "... " : ">>> "; 90 | 91 | printf("\e[2K\r%s%s", psx, furi_string_get_cstr(context->line)); 92 | 93 | fflush(stdout); 94 | 95 | for(size_t i = context->cursor; i < furi_string_size(context->line); i++) { 96 | printf("\e[D"); 97 | } 98 | 99 | fflush(stdout); 100 | } 101 | 102 | inline static void handle_arrow_keys(char character, mp_flipper_repl_context_t* context) { 103 | mp_flipper_repl_history_t* history = context->history; 104 | 105 | do { 106 | bool update_by_history = false; 107 | // up arrow 108 | if(character == 'A' && history->pointer == 0) { 109 | furi_string_set(history->stack[0], context->line); 110 | } 111 | 112 | if(character == 'A' && history->pointer < history->size) { 113 | history->pointer += (history->pointer + 1) == history->size ? 0 : 1; 114 | 115 | update_by_history = true; 116 | } 117 | 118 | // down arrow 119 | if(character == 'B' && history->pointer > 0) { 120 | history->pointer--; 121 | 122 | update_by_history = true; 123 | } 124 | 125 | if(update_by_history) { 126 | furi_string_set(context->line, history->stack[history->pointer]); 127 | 128 | context->cursor = furi_string_size(context->line); 129 | 130 | break; 131 | } 132 | 133 | // right arrow 134 | if(character == 'C' && context->cursor != furi_string_size(context->line)) { 135 | context->cursor++; 136 | 137 | break; 138 | } 139 | 140 | // left arrow 141 | if(character == 'D' && context->cursor > 0) { 142 | context->cursor--; 143 | 144 | break; 145 | } 146 | } while(false); 147 | 148 | print_full_psx(context); 149 | } 150 | 151 | inline static void handle_backspace(mp_flipper_repl_context_t* context) { 152 | // skip backspace at begin of line 153 | if(context->cursor == 0) { 154 | return; 155 | } 156 | 157 | const char* line = furi_string_get_cstr(context->line); 158 | size_t before = context->cursor - 1; 159 | size_t after = furi_string_size(context->line) - context->cursor; 160 | 161 | furi_string_printf(context->line, "%.*s%.*s", before, line, after, line + context->cursor); 162 | 163 | context->cursor--; 164 | 165 | printf("\e[D\e[1P"); 166 | 167 | fflush(stdout); 168 | } 169 | 170 | inline static bool is_indent_required(mp_flipper_repl_context_t* context) { 171 | for(size_t i = 0; context->is_ps2 && i < context->cursor; i++) { 172 | if(furi_string_get_char(context->line, i) != ' ') { 173 | return false; 174 | } 175 | } 176 | 177 | return context->is_ps2; 178 | } 179 | 180 | inline static void handle_autocomplete(mp_flipper_repl_context_t* context) { 181 | // check if ps2 is active and just a tab character is required 182 | if(is_indent_required(context)) { 183 | furi_string_replace_at(context->line, context->cursor, 0, " "); 184 | context->cursor += 4; 185 | 186 | print_full_psx(context); 187 | 188 | return; 189 | } 190 | 191 | const char* new_line = furi_string_get_cstr(context->line); 192 | FuriString* orig_line = furi_string_alloc_printf("%s", new_line); 193 | const char* orig_line_str = furi_string_get_cstr(orig_line); 194 | 195 | char* completion = malloc(128 * sizeof(char)); 196 | 197 | mp_print_t* print = malloc(sizeof(mp_print_t)); 198 | 199 | print->data = mp_flipper_print_data_alloc(); 200 | print->print_strn = mp_flipper_print_strn; 201 | 202 | size_t length = mp_flipper_repl_autocomplete(new_line, context->cursor, print, &completion); 203 | 204 | do { 205 | if(length == 0) { 206 | break; 207 | } 208 | 209 | if(length != AUTOCOMPLETE_MANY_MATCHES) { 210 | furi_string_printf( 211 | context->line, 212 | "%.*s%.*s%s", 213 | context->cursor, 214 | orig_line_str, 215 | length, 216 | completion, 217 | orig_line_str + context->cursor); 218 | 219 | context->cursor += length; 220 | } else { 221 | printf("%s", mp_flipper_print_get_data(print->data)); 222 | } 223 | 224 | print_full_psx(context); 225 | } while(false); 226 | 227 | mp_flipper_print_data_free(print->data); 228 | furi_string_free(orig_line); 229 | free(completion); 230 | free(print); 231 | } 232 | 233 | inline static void update_history(mp_flipper_repl_context_t* context) { 234 | mp_flipper_repl_history_t* history = context->history; 235 | 236 | if(!furi_string_empty(context->line) && !furi_string_equal(context->line, history->stack[1])) { 237 | history->size += history->size == HISTORY_SIZE ? 0 : 1; 238 | 239 | for(size_t i = history->size - 1; i > 1; i--) { 240 | furi_string_set(history->stack[i], history->stack[i - 1]); 241 | } 242 | 243 | furi_string_set(history->stack[1], context->line); 244 | } 245 | 246 | furi_string_reset(history->stack[0]); 247 | 248 | history->pointer = 0; 249 | } 250 | 251 | inline static bool continue_with_input(mp_flipper_repl_context_t* context) { 252 | if(furi_string_empty(context->line)) { 253 | return false; 254 | } 255 | 256 | if(!mp_flipper_repl_continue_with_input(furi_string_get_cstr(context->code))) { 257 | return false; 258 | } 259 | 260 | return true; 261 | } 262 | 263 | void upython_repl_execute() { 264 | size_t stack; 265 | 266 | const size_t heap_size = memmgr_get_free_heap() * 0.1; 267 | const size_t stack_size = 2 * 1024; 268 | uint8_t* heap = malloc(heap_size * sizeof(uint8_t)); 269 | 270 | printf("MicroPython (%s, %s) on Flipper Zero\r\n", MICROPY_GIT_TAG, MICROPY_BUILD_DATE); 271 | printf("Quit: Ctrl+D | Heap: %zu bytes | Stack: %zu bytes\r\n", heap_size, stack_size); 272 | printf(" To do a reboot, press Left+Back for 5 seconds.\r\n"); 273 | printf("Docs: https://ofabel.github.io/mp-flipper\r\n"); 274 | 275 | mp_flipper_repl_context_t* context = mp_flipper_repl_context_alloc(); 276 | 277 | mp_flipper_set_root_module_path("/ext"); 278 | mp_flipper_init(heap, heap_size, stack_size, &stack); 279 | 280 | char character = '\0'; 281 | 282 | uint8_t* buffer = malloc(sizeof(uint8_t)); 283 | 284 | bool exit = false; 285 | 286 | // REPL loop 287 | do { 288 | furi_string_reset(context->code); 289 | 290 | context->is_ps2 = false; 291 | 292 | // scan line loop 293 | do { 294 | furi_string_reset(context->line); 295 | 296 | context->cursor = 0; 297 | 298 | print_full_psx(context); 299 | 300 | // scan character loop 301 | do { 302 | character = getc(stdin); 303 | 304 | // Ctrl + C 305 | if(character == CliSymbolAsciiETX) { 306 | context->cursor = 0; 307 | 308 | furi_string_reset(context->line); 309 | furi_string_reset(context->code); 310 | 311 | printf("\r\nKeyboardInterrupt\r\n"); 312 | 313 | break; 314 | } 315 | 316 | // Ctrl + D 317 | if(character == CliSymbolAsciiEOT) { 318 | exit = true; 319 | 320 | break; 321 | } 322 | 323 | // skip line feed 324 | if(character == CliSymbolAsciiLF) { 325 | continue; 326 | } 327 | 328 | // handle carriage return 329 | if(character == CliSymbolAsciiCR) { 330 | furi_string_push_back(context->code, '\n'); 331 | furi_string_cat(context->code, context->line); 332 | furi_string_trim(context->code); 333 | 334 | printf("\r\n"); 335 | 336 | break; 337 | } 338 | 339 | // handle arrow keys 340 | if(character >= 0x18 && character <= 0x1B) { 341 | character = getc(stdin); 342 | character = getc(stdin); 343 | 344 | handle_arrow_keys(character, context); 345 | 346 | continue; 347 | } 348 | 349 | // handle tab, do autocompletion 350 | if(character == CliSymbolAsciiTab) { 351 | handle_autocomplete(context); 352 | 353 | continue; 354 | } 355 | 356 | // handle backspace 357 | if(character == CliSymbolAsciiBackspace || character == CliSymbolAsciiDel) { 358 | handle_backspace(context); 359 | 360 | continue; 361 | } 362 | 363 | // append at end 364 | if(context->cursor == furi_string_size(context->line)) { 365 | buffer[0] = character; 366 | putc(buffer[0], stdout); 367 | fflush(stdout); 368 | 369 | furi_string_push_back(context->line, character); 370 | 371 | context->cursor++; 372 | 373 | continue; 374 | } 375 | 376 | // insert between 377 | if(context->cursor < furi_string_size(context->line)) { 378 | const char temp[2] = {character, 0}; 379 | furi_string_replace_at(context->line, context->cursor++, 0, temp); 380 | 381 | printf("\e[4h%c\e[4l", character); 382 | fflush(stdout); 383 | 384 | continue; 385 | } 386 | } while(true); 387 | 388 | // Ctrl + D 389 | if(exit) { 390 | break; 391 | } 392 | 393 | update_history(context); 394 | } while((context->is_ps2 = continue_with_input(context))); 395 | 396 | // Ctrl + D 397 | if(exit) { 398 | break; 399 | } 400 | 401 | mp_flipper_exec_str(furi_string_get_cstr(context->code)); 402 | } while(true); 403 | 404 | mp_flipper_deinit(); 405 | 406 | mp_flipper_repl_context_free(context); 407 | 408 | free(heap); 409 | free(buffer); 410 | } 411 | -------------------------------------------------------------------------------- /upython_splash.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | #include 12 | 13 | #include "upython.h" 14 | #include "upython_icons.h" 15 | 16 | static void on_input(const void* event, void* ctx) { 17 | UNUSED(ctx); 18 | 19 | InputKey key = ((InputEvent*)event)->key; 20 | InputType type = ((InputEvent*)event)->type; 21 | 22 | if(type != InputTypeRelease) { 23 | return; 24 | } 25 | 26 | switch(key) { 27 | case InputKeyOk: 28 | action = ActionOpen; 29 | break; 30 | case InputKeyBack: 31 | action = ActionExit; 32 | break; 33 | default: 34 | action = ActionNone; 35 | break; 36 | } 37 | } 38 | 39 | bool upython_confirm_exit_action() { 40 | DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS); 41 | 42 | DialogMessage* message = dialog_message_alloc(); 43 | 44 | dialog_message_set_text(message, "Close uPython?", 64, 32, AlignCenter, AlignCenter); 45 | dialog_message_set_buttons(message, "Yes", NULL, "No"); 46 | 47 | DialogMessageButton button = dialog_message_show(dialogs, message); 48 | 49 | dialog_message_free(message); 50 | 51 | furi_record_close(RECORD_DIALOGS); 52 | 53 | return button == DialogMessageButtonLeft; 54 | } 55 | 56 | bool upython_select_python_file(FuriString* file_path) { 57 | DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS); 58 | 59 | DialogsFileBrowserOptions browser_options; 60 | 61 | dialog_file_browser_set_basic_options(&browser_options, "py", NULL); 62 | 63 | browser_options.hide_ext = false; 64 | browser_options.base_path = STORAGE_APP_DATA_PATH_PREFIX; 65 | 66 | bool result = dialog_file_browser_show(dialogs, file_path, file_path, &browser_options); 67 | 68 | furi_record_close(RECORD_DIALOGS); 69 | 70 | return result; 71 | } 72 | 73 | Action upython_splash_screen() { 74 | if(action != ActionNone) { 75 | return action; 76 | } 77 | 78 | Gui* gui = furi_record_open(RECORD_GUI); 79 | FuriPubSub* input_event_queue = furi_record_open(RECORD_INPUT_EVENTS); 80 | FuriPubSubSubscription* input_event = furi_pubsub_subscribe(input_event_queue, on_input, NULL); 81 | 82 | Canvas* canvas = gui_direct_draw_acquire(gui); 83 | 84 | canvas_draw_icon(canvas, 0, 0, &I_splash); 85 | canvas_draw_icon(canvas, 82, 17, &I_qrcode); 86 | canvas_set_color(canvas, ColorBlack); 87 | canvas_set_font(canvas, FontSecondary); 88 | canvas_draw_str_aligned(canvas, 66, 3, AlignLeft, AlignTop, "Micro"); 89 | canvas_set_font(canvas, FontPrimary); 90 | canvas_draw_str_aligned(canvas, 90, 2, AlignLeft, AlignTop, "Python"); 91 | 92 | canvas_set_font(canvas, FontSecondary); 93 | 94 | canvas_draw_icon(canvas, 65, 53, &I_Pin_back_arrow_10x8); 95 | canvas_draw_str_aligned(canvas, 78, 54, AlignLeft, AlignTop, "Exit"); 96 | 97 | canvas_draw_icon(canvas, 98, 54, &I_ButtonCenter_7x7); 98 | canvas_draw_str_aligned(canvas, 107, 54, AlignLeft, AlignTop, "Open"); 99 | 100 | canvas_commit(canvas); 101 | 102 | while(action == ActionNone) { 103 | furi_delay_ms(1); 104 | } 105 | 106 | furi_pubsub_unsubscribe(input_event_queue, input_event); 107 | 108 | gui_direct_draw_release(gui); 109 | 110 | furi_record_close(RECORD_INPUT_EVENTS); 111 | furi_record_close(RECORD_GUI); 112 | 113 | return action; 114 | } 115 | --------------------------------------------------------------------------------