Bläddra i källkod

update upython app

MX 1 år sedan
förälder
incheckning
b360f0d8ed
67 ändrade filer med 5319 tillägg och 571 borttagningar
  1. 2 0
      .gitignore
  2. 42 2
      CHANGELOG.md
  3. 19 20
      README.md
  4. 5 2
      application.fam
  5. 14 0
      docs/CHANGELOG.md
  6. 233 0
      docs/pages/assets/repl.yml
  7. 18 3
      docs/pages/conf.py
  8. 6 5
      docs/pages/features.md
  9. 5 2
      docs/pages/index.rst
  10. 0 1
      docs/pages/quickstart.md
  11. 51 0
      docs/pages/quickstart.rst
  12. 90 7
      docs/pages/reference.rst
  13. 1 0
      docs/pages/roadmap.md
  14. 15 0
      examples/logger.py
  15. 5 0
      examples/open.py
  16. 16 0
      examples/uart.py
  17. 14 0
      flipper.json
  18. 164 0
      flipperzero/_uart.py
  19. 366 0
      flipperzero/io.py
  20. 162 0
      flipperzero/logging.py
  21. 2 0
      lib/micropython-port/mp_flipper_context.h
  22. 9 6
      lib/micropython-port/mp_flipper_file_helper.c
  23. 23 22
      lib/micropython-port/mp_flipper_file_reader.c
  24. 89 0
      lib/micropython-port/mp_flipper_fileio.c
  25. 2 2
      lib/micropython-port/mp_flipper_halport.c
  26. 50 0
      lib/micropython-port/mp_flipper_logging.c
  27. 81 0
      lib/micropython-port/mp_flipper_modflipperzero_uart.c
  28. 15 3
      lib/micropython-port/mp_flipper_polyfill.c
  29. 9 3
      lib/micropython-port/mp_flipper_runtime.c
  30. 10 0
      lib/micropython/genhdr/moduledefs.h
  31. 3 3
      lib/micropython/genhdr/mpversion.h
  32. 36 2
      lib/micropython/genhdr/qstrdefs.generated.h
  33. 1 42
      lib/micropython/mp_flipper_compiler.c
  34. 0 211
      lib/micropython/mp_flipper_config.h
  35. 0 7
      lib/micropython/mp_flipper_config_fap.h
  36. 0 8
      lib/micropython/mp_flipper_config_firmware.h
  37. 1 0
      lib/micropython/mp_flipper_file_reader.c
  38. 284 0
      lib/micropython/mp_flipper_fileio.c
  39. 25 0
      lib/micropython/mp_flipper_fileio.h
  40. 5 9
      lib/micropython/mp_flipper_halport.c
  41. 0 2
      lib/micropython/mp_flipper_halport.h
  42. 110 0
      lib/micropython/mp_flipper_logging.c
  43. 14 0
      lib/micropython/mp_flipper_logging.h
  44. 132 0
      lib/micropython/mp_flipper_modflipperzero.c
  45. 9 0
      lib/micropython/mp_flipper_modflipperzero.h
  46. 12 0
      lib/micropython/mp_flipper_repl.c
  47. 12 0
      lib/micropython/mp_flipper_repl.h
  48. 4 32
      lib/micropython/mp_flipper_runtime.c
  49. 0 3
      lib/micropython/mp_flipper_runtime.h
  50. 162 5
      lib/micropython/mpconfigport.h
  51. 23 0
      lib/micropython/py/dynruntime.h
  52. 7 1
      lib/micropython/py/mpconfig.h
  53. 4 0
      lib/micropython/py/nativeglue.c
  54. 5 1
      lib/micropython/py/nativeglue.h
  55. 9 1
      lib/micropython/py/nlrthumb.c
  56. 14 14
      lib/micropython/py/obj.h
  57. 16 4
      lib/micropython/py/objarray.c
  58. 4 4
      lib/micropython/py/objfun.h
  59. 2185 0
      package-lock.json
  60. 5 0
      package.json
  61. 1 1
      pyproject.toml
  62. 40 143
      upython.c
  63. 35 0
      upython.h
  64. 82 0
      upython_cli.c
  65. 53 0
      upython_file.c
  66. 394 0
      upython_repl.c
  67. 114 0
      upython_splash.c

+ 2 - 0
.gitignore

@@ -1,6 +1,8 @@
 /dist/
 /dist/
 /venv/
 /venv/
+/node_modules/
 /flipperzero/__init__.py
 /flipperzero/__init__.py
+/fssdk
 .vscode
 .vscode
 .clang-format
 .clang-format
 .clangd
 .clangd

+ 42 - 2
CHANGELOG.md

@@ -7,7 +7,45 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 
 ## [Unreleased]
 ## [Unreleased]
 
 
-## [1.3.0]
+## [1.5.0] - 2024-10-06
+
+### Added
+
+* Support for basic file system operations using the `io` module:
+  * Read and write files.
+  * Open in text or binary mode.
+* Simple `logging` module:
+  * Log levels according to the Flipper Zero API: trace, debug, info, warn, error.
+  * Only the root logger is supported, so no `getLogger` function.
+  * Logs directly to the log output, so no output in the REPL.
+* Redirect output of `print` statements:
+  * To `stdout` when a script is invoked by `py` command from the CLI.
+  * To the log buffer, if a script is invoked from the UI.
+* UART support for the `flipperzero` module.
+
+### Changed
+
+* The `py` command waits until the script terminates.
+
+### Fixed
+
+* [#3](https://github.com/ofabel/mp-flipper/issues/3): Proper `CR` and `LF` handling in the REPL.
+
+## [1.4.0] - 2024-09-29
+
+### Added
+
+* Allow passing the path to the script to execute as a CLI argument.
+* Open a REPL from the CLI interface by using the `py` command:
+  * The `py` command is only available while the app is running.
+  * You cannot run a Python script and use the REPL at the same time.
+  * You can also start a Python script with the `py` command while the app is idle.
+
+### Changed
+
+* MicroPython update to version 1.23.0.
+
+## [1.3.0] - 2024-09-08
 
 
 ### Added
 ### Added
 
 
@@ -118,7 +156,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 * Basic build setup
 * Basic build setup
 * Minimal working example
 * Minimal working example
 
 
-[Unreleased]: https://github.com/ofabel/mp-flipper/compare/v1.3.0...HEAD
+[Unreleased]: https://github.com/ofabel/mp-flipper/compare/v1.5.0...dev
+[1.5.0]: https://github.com/ofabel/mp-flipper/compare/v1.4.0...v1.5.0
+[1.4.0]: https://github.com/ofabel/mp-flipper/compare/v1.3.0...v1.4.0
 [1.3.0]: https://github.com/ofabel/mp-flipper/compare/v1.2.0...v1.3.0
 [1.3.0]: https://github.com/ofabel/mp-flipper/compare/v1.2.0...v1.3.0
 [1.2.0]: https://github.com/ofabel/mp-flipper/compare/v1.1.0...v1.2.0
 [1.2.0]: https://github.com/ofabel/mp-flipper/compare/v1.1.0...v1.2.0
 [1.1.0]: https://github.com/ofabel/mp-flipper/compare/v1.0.0...v1.1.0
 [1.1.0]: https://github.com/ofabel/mp-flipper/compare/v1.0.0...v1.1.0

+ 19 - 20
README.md

@@ -4,25 +4,11 @@
 
 
 # MicroPython Flipper Zero
 # MicroPython Flipper Zero
 
 
-The application is now available on the official [Flipper Lab](https://lab.flipper.net/apps/upython).
-For more information on how to programm your Flipper with Python, check out the [documentation](https://ofabel.github.io/mp-flipper/) on GitHub pages.
+Allows you to use the power of Python natively on your Flipper Zero.
+The application is available on the official [Flipper Lab](https://lab.flipper.net/apps/upython).
+For details on how to programm your Flipper with Python, check out the [documentation](https://ofabel.github.io/mp-flipper/) on GitHub pages.
 
 
-This branch contains the [FAP](https://developer.flipper.net/flipperzero/doxygen/apps_on_sd_card.html) version of the [MicroPython](https://micropython.org/) support for the famous [Flipper Zero](https://flipperzero.one/) gadget.
-The results of the preceding research phase is still available in the [poc](https://github.com/ofabel/mp-flipper/tree/poc) branch.
-The [lib](https://github.com/ofabel/mp-flipper/tree/lib) branch of this repository contains just the MicroPython library.
-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).
-
-## Usage
-
-Just place your Python files somewhere on the SD card (e.g. by using the [qFlipper](https://flipperzero.one/downloads) app).
-
-The application just starts with an open file browser:
-
-![](./assets/file-browser.png)
-
-Here you can select any Python file to compile and execute from the SD card:
-
-![](./assets/tic-tac-toe.png)
+![MicroPython REPL](./docs/pages/assets/repl.gif)
 
 
 ## Disclaimer
 ## Disclaimer
 
 
@@ -30,12 +16,25 @@ This FAP version requires about 80 kB from SRAM to start (needed for the Python
 Due to memory fragmentation it's possible, that the application crashes when you start it.
 Due to memory fragmentation it's possible, that the application crashes when you start it.
 If this happens, just try again (the crash doesn't harm your device).
 If this happens, just try again (the crash doesn't harm your device).
 
 
-Sadly, REPL support is only available in fhe [firmware fork](https://github.com/ofabel/flipperzero-firmware/tree/ofa/micropython) version.
+> [!IMPORTANT]
+> This problem is already addressed to the firmware developers in [this issue](https://github.com/flipperdevices/flipperzero-firmware/issues/3927).
+> Nevertheless, running the uPython application from the SD card is still a heavy task for the Flipper.
+
+_I'm thinking about publishing a fork of the original firmware with uPython bundled as a core service._
+_This would mitigate all the memory problems._
+_The SD card version would still be there and maintained, but possibly with a limited set of features._
 
 
-## Setup and Build
+## Development
 
 
 This section is only relevant, if you want to build the FAP on your own.
 This section is only relevant, if you want to build the FAP on your own.
 
 
+### Branches
+
+This branch contains the [FAP](https://developer.flipper.net/flipperzero/doxygen/apps_on_sd_card.html) version.
+The results of the preceding research phase is still available in the [poc](https://github.com/ofabel/mp-flipper/tree/poc) branch.
+The [lib](https://github.com/ofabel/mp-flipper/tree/lib) branch of this repository contains just the [MicroPython](https://github.com/micropython/micropython) library.
+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).
+
 ### Requirements
 ### Requirements
 
 
 * [Git](https://git-scm.com/)
 * [Git](https://git-scm.com/)

+ 5 - 2
application.fam

@@ -5,7 +5,7 @@ App(
     entry_point="upython",
     entry_point="upython",
     stack_size=4 * 1024,
     stack_size=4 * 1024,
     fap_category="Tools",
     fap_category="Tools",
-    fap_version="1.3",
+    fap_version="1.5",
     fap_description="Compile and execute MicroPython scripts",
     fap_description="Compile and execute MicroPython scripts",
     fap_icon="icon.png",
     fap_icon="icon.png",
     fap_icon_assets="images",
     fap_icon_assets="images",
@@ -16,8 +16,11 @@ App(
         "*.c*",
         "*.c*",
         "!./lib/micropython",
         "!./lib/micropython",
         "!./lib/micropython-port",
         "!./lib/micropython-port",
-        "!./docs/pages",
         "!./flipperzero",
         "!./flipperzero",
+        "!./node_modules",
+        "!./examples",
+        "!./assets",
+        "!./docs",
         "!./venv",
         "!./venv",
         "!./dist",
         "!./dist",
     ],
     ],

+ 14 - 0
docs/CHANGELOG.md

@@ -1,3 +1,17 @@
+## 1.5
+
+* Added **io** module for basic file system operations.
+* Added **logging** module to allow level based log messages.
+* Rework of the **print** function: output redirection, based on script invocation.
+* Added UART support: connect, read and write.
+* Fixed the line feed handling in the REPL.
+
+## 1.4
+
+* Added interactive Python shell (aka REPL) as a CLI command.
+* Allow passing a path to a script to execute as a CLI argument.
+* Updated MicroPython to version 1.23.0.
+
 ## 1.3
 ## 1.3
 
 
 * Added simple ADC support: read value and voltage.
 * Added simple ADC support: read value and voltage.

+ 233 - 0
docs/pages/assets/repl.yml

@@ -0,0 +1,233 @@
+# The configurations that used for the recording, feel free to edit them
+config:
+
+  # Specify a command to be executed
+  # like `/bin/bash -l`, `ls`, or any other commands
+  # the default is bash for Linux
+  # or powershell.exe for Windows
+  command: bash -l
+  
+  # Specify the current working directory path
+  # the default is the current working directory path
+  cwd: ~/dev/scripts
+  
+  # Export additional ENV variables
+  env:
+    recording: true
+  
+  # Explicitly set the number of columns
+  # or use `auto` to take the current
+  # number of columns of your shell
+  cols: 80
+  
+  # Explicitly set the number of rows
+  # or use `auto` to take the current
+  # number of rows of your shell
+  rows: 17
+  
+  # Amount of times to repeat GIF
+  # If value is -1, play once
+  # If value is 0, loop indefinitely
+  # If value is a positive number, loop n times
+  repeat: 0
+  
+  # Quality
+  # 1 - 100
+  quality: 100
+  
+  # Delay between frames in ms
+  # If the value is `auto` use the actual recording delays
+  frameDelay: auto
+  
+  # Maximum delay between frames in ms
+  # Ignored if the `frameDelay` isn't set to `auto`
+  # Set to `auto` to prevent limiting the max idle time
+  maxIdleTime: 2000
+  
+  # The surrounding frame box
+  # The `type` can be null, window, floating, or solid`
+  # To hide the title use the value null
+  # Don't forget to add a backgroundColor style with a null as type
+  frameBox:
+    type: floating
+    title: null
+    style:
+      border: 0px black solid
+      # boxShadow: none
+      # margin: 0px
+  
+  # Add a watermark image to the rendered gif
+  # You need to specify an absolute path for
+  # the image on your machine or a URL, and you can also
+  # add your own CSS styles
+  watermark:
+    imagePath: null
+    style:
+      position: absolute
+      right: 15px
+      bottom: 15px
+      width: 100px
+      opacity: 0.9
+  
+  # Cursor style can be one of
+  # `block`, `underline`, or `bar`
+  cursorStyle: block
+  
+  # Font family
+  # You can use any font that is installed on your machine
+  # in CSS-like syntax
+  fontFamily: "Monaco, Lucida Console, Ubuntu Mono, Monospace"
+  
+  # The size of the font
+  fontSize: 12
+  
+  # The height of lines
+  lineHeight: 1
+  
+  # The spacing between letters
+  letterSpacing: 0
+  
+  # Theme
+  theme:
+    background: "transparent"
+    foreground: "#afafaf"
+    cursor: "#c7c7c7"
+    black: "#232628"
+    red: "#fc4384"
+    green: "#b3e33b"
+    yellow: "#ffa727"
+    blue: "#75dff2"
+    magenta: "#ae89fe"
+    cyan: "#708387"
+    white: "#d5d5d0"
+    brightBlack: "#626566"
+    brightRed: "#ff7fac"
+    brightGreen: "#c8ed71"
+    brightYellow: "#ebdf86"
+    brightBlue: "#75dff2"
+    brightMagenta: "#ae89fe"
+    brightCyan: "#b1c6ca"
+    brightWhite: "#f9f9f4"
+  
+# Records, feel free to edit them
+records:
+  - delay: 0
+    content: "\r"
+  - delay: 35
+    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>: "
+  - delay: 1259
+    content: p
+  - delay: 105
+    content: 'y'
+  - delay: 1112
+    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"
+  - delay: 5
+    content: "\e[2K\r>>> "
+  - delay: 2144
+    content: p
+  - delay: 195
+    content: r
+  - delay: 194
+    content: i
+  - delay: 53
+    content: 'n'
+  - delay: 55
+    content: t
+  - delay: 80
+    content: (
+  - delay: 80
+    content: ''''
+  - delay: 100
+    content: H
+  - delay: 100
+    content: e
+  - delay: 134
+    content: l
+  - delay: 149
+    content: l
+  - delay: 254
+    content: o
+  - delay: 107
+    content: ' '
+  - delay: 250
+    content: f
+  - delay: 161
+    content: r
+  - delay: 120
+    content: o
+  - delay: 112
+    content: m
+  - delay: 53
+    content: ' '
+  - delay: 30
+    content: R
+  - delay: 104
+    content: E
+  - delay: 247
+    content: P
+  - delay: 256
+    content: L
+  - delay: 250
+    content: ''''
+  - delay: 250
+    content: )
+  - delay: 541
+    content: "\r\r\nHello from REPL\r\n\e[2K\r>>> "
+  - delay: 3126
+    content: i
+  - delay: 82
+    content: m
+  - delay: 300
+    content: p
+  - delay: 300
+    content: "\e[2K\r>>> import "
+  - delay: 500
+    content: f
+  - delay: 82
+    content: l
+  - delay: 317
+    content: "\e[2K\r>>> import flipperzero"
+  - delay: 300
+    content: ' '
+  - delay: 144
+    content: a
+  - delay: 75
+    content: s
+  - delay: 300
+    content: ' '
+  - delay: 208
+    content: f
+  - delay: 121
+    content: '0'
+  - delay: 451
+    content: "\r\r\n\e[2K\r>>> "
+  - delay: 845
+    content: f
+  - delay: 75
+    content: '0'
+  - delay: 248
+    content: .
+  - delay: 307
+    content: i
+  - delay: 53
+    content: 'n'
+  - delay: 459
+    content: "\e[2K\r>>> f0.infrared_"
+  - delay: 1072
+    content: "\r\ninfrared_is_busy                infrared_receive\r\ninfrared_transmit\r\n\e[2K\r>>> f0.infrared_"
+  - delay: 1169
+    content: r
+  - delay: 61
+    content: e
+  - delay: 984
+    content: "\e[2K\r>>> f0.infrared_receive"
+  - delay: 814
+    content: (
+  - delay: 172
+    content: )
+  - delay: 1262
+    content: "\r\r\n"
+  - delay: 2086
+    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>>> "
+  - delay: 3424
+    content: "\e[2K\r>>> "

+ 18 - 3
docs/pages/conf.py

@@ -5,16 +5,31 @@ import sys
 base = pathlib.Path(__file__).parent.parent.parent
 base = pathlib.Path(__file__).parent.parent.parent
 root = base.__str__()
 root = base.__str__()
 flipperzero = base.joinpath('flipperzero').__str__()
 flipperzero = base.joinpath('flipperzero').__str__()
-now = datetime.datetime.now()
 
 
 sys.path.append(root)
 sys.path.append(root)
 sys.path.append(flipperzero)
 sys.path.append(flipperzero)
 
 
+def copy_dict(source, target):
+    for key, value in source.__dict__.items():
+        target.__dict__[key] = value
+
+import flipperzero.logging
+import logging
+
+copy_dict(flipperzero.logging, logging)
+
+import flipperzero.io
+import io
+
+copy_dict(flipperzero.io, io)
+
+now = datetime.datetime.now()
+
 project = 'uPython'
 project = 'uPython'
 copyright = str(now.year) + ', Oliver Fabel'
 copyright = str(now.year) + ', Oliver Fabel'
 author = 'Oliver Fabel'
 author = 'Oliver Fabel'
-release = '1.1.0'
-version = '1.1'
+release = '1.5.0'
+version = '1.5'
 language = 'en'
 language = 'en'
 
 
 extensions = [
 extensions = [

+ 6 - 5
docs/pages/features.md

@@ -8,27 +8,31 @@ So here is a detailed list of all supported and unsupported Python language feat
 The following features are enabled and supported by the interpreter:
 The following features are enabled and supported by the interpreter:
 
 
 * Garbage collector is enabled.
 * Garbage collector is enabled.
+* Finaliser calls in the garbage collector (e.g. `__del__`).
 * The `__file__` constant.
 * The `__file__` constant.
 * Import of external files from the SD card.
 * Import of external files from the SD card.
 * Read and write files from and to the SD card.
 * Read and write files from and to the SD card.
 * The `time` module.
 * The `time` module.
 * The `random` module.
 * The `random` module.
+* The `logging` module.
+* The `io` module.
 * The `float` data type.
 * The `float` data type.
+* The `%` string formatting operator.
+* The `.format` string formatting function.
 * Support for [decorator](https://docs.python.org/3/glossary.html#term-decorator) functions.
 * Support for [decorator](https://docs.python.org/3/glossary.html#term-decorator) functions.
 * The `setattr` function.
 * The `setattr` function.
 * The `filter` function.
 * The `filter` function.
 * The `reversed` function.
 * The `reversed` function.
 * The `min` and `max` function.
 * The `min` and `max` function.
 * Module-level `__init__` imports.
 * Module-level `__init__` imports.
+* Support for a [REPL](https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop).
 
 
 ## Unsupported
 ## Unsupported
 
 
 The following features are disabled and _not_ supported by the interpreter:
 The following features are disabled and _not_ supported by the interpreter:
 
 
-* Finaliser calls in the garbage collector (e.g. `__del__`).
 * The `__doc__` constants.
 * The `__doc__` constants.
 * Source code line numbers in exceptions.
 * Source code line numbers in exceptions.
-* Support for a [REPL](https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop).
 * The `cmath` module.
 * The `cmath` module.
 * The `complex` data type.
 * The `complex` data type.
 * Support for multiple inheritance.
 * Support for multiple inheritance.
@@ -40,15 +44,12 @@ The following features are disabled and _not_ supported by the interpreter:
 * Support for `bytes.hex` and `bytes.fromhex`.
 * Support for `bytes.hex` and `bytes.fromhex`.
 * Support for unicode characters.
 * Support for unicode characters.
 * The string functions `.center`, `.count`, `.partition`, `.rpartition` and `.splitlines`.
 * The string functions `.center`, `.count`, `.partition`, `.rpartition` and `.splitlines`.
-* The `%` string formatting operator (use `.format` instead).
 * The `bytearray` data type.
 * The `bytearray` data type.
 * The `memoryview` data type.
 * The `memoryview` data type.
 * The `slice` object.
 * The `slice` object.
 * The `frozenset` object.
 * The `frozenset` object.
 * The `property` decorator.
 * The `property` decorator.
-* The `range` function with `start`, `stop` and `step` attributes.
 * The `next` function with a second argument.
 * The `next` function with a second argument.
-* The `round` function with integers.
 * All special methods for user classes (e.g. `__imul__`).
 * All special methods for user classes (e.g. `__imul__`).
 * The `enumerate` function.
 * The `enumerate` function.
 * The `compile` function.
 * The `compile` function.

+ 5 - 2
docs/pages/index.rst

@@ -25,12 +25,15 @@ MicroPython on Flipper Zero
 A `MicroPython <https://github.com/micropython/micropython>`_ port for the famous `Flipper Zero <https://flipperzero.one/>`_.
 A `MicroPython <https://github.com/micropython/micropython>`_ port for the famous `Flipper Zero <https://flipperzero.one/>`_.
 No need to learn C: Use your favourite programming language to create apps, games and scripts.
 No need to learn C: Use your favourite programming language to create apps, games and scripts.
 
 
+.. image:: ./assets/repl.gif
+   :align: center
+
 Features
 Features
 --------
 --------
 
 
 * Support for basic language constructs like functions, classes, loops, ...
 * Support for basic language constructs like functions, classes, loops, ...
 * Access the Flipper's hardware: buttons, speaker, LED, GPIO, ADC, PWM ...
 * Access the Flipper's hardware: buttons, speaker, LED, GPIO, ADC, PWM ...
-* No custom firmware required, so no risk to brick your Flipper.
+* No custom firmware is required, so no risk to brick your Flipper.
 
 
 A complete list can be found in the :doc:`features </features>` section.
 A complete list can be found in the :doc:`features </features>` section.
 
 
@@ -40,7 +43,7 @@ How to Start
 1. Install the application from the `Flipper Lab <https://lab.flipper.net/apps/upython>`_ on your Flipper device.
 1. Install the application from the `Flipper Lab <https://lab.flipper.net/apps/upython>`_ on your Flipper device.
 2. Write some Python code or use one of the provided `examples <https://github.com/ofabel/mp-flipper/tree/master/examples>`_.
 2. Write some Python code or use one of the provided `examples <https://github.com/ofabel/mp-flipper/tree/master/examples>`_.
 3. Use the `qFlipper <https://flipperzero.one/update>`_ application to upload the code to your Flipper's SD card.
 3. Use the `qFlipper <https://flipperzero.one/update>`_ application to upload the code to your Flipper's SD card.
-4. Use the **uPython** application on your Flipper to execute your Python script.
+4. Start the **uPython** application on your Flipper to execute your Python script.
 
 
 Checkout the :doc:`reference </reference>` section for an in-depth API documentation.
 Checkout the :doc:`reference </reference>` section for an in-depth API documentation.
 
 

+ 0 - 1
docs/pages/quickstart.md

@@ -1 +0,0 @@
-# Quickstart

+ 51 - 0
docs/pages/quickstart.rst

@@ -0,0 +1,51 @@
+Quickstart
+==========
+
+This page provides some details on how to start and use the application.
+
+Basics
+------
+
+How to install the application and run a script:
+
+1. Install the application from the `Flipper Lab <https://lab.flipper.net/apps/upython>`_ on your Flipper device.
+2. Write some Python code or use one of the provided `examples <https://github.com/ofabel/mp-flipper/tree/master/examples>`_.
+3. Use the `qFlipper <https://flipperzero.one/update>`_ application to upload the code to your Flipper's SD card.
+4. Start the **uPython** application on your Flipper to run your Python script.
+
+Instead of running a script, you could also use the interactive MicroPython shell from the terminal.
+Visit the `Flipper documentation <https://docs.flipper.net/development/cli>`_ for details about the CLI in general.
+
+.. hint::
+
+   Looking for a more efficient solution to copy your files or start a CLI session?
+   Try the `Flipper Zero Script SDK <https://github.com/ofabel/fssdk>`_ helper.
+
+In case your Flipper is not responding or a script doesn't behave as expected and won't finish: `do a reboot <https://docs.flipper.net/basics/reboot>`_ by pressing and holding **Left** and **Back** for 5 seconds.
+
+Usage
+-----
+
+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.
+
+You can use the CLI to start the application:
+
+.. code-block:: shell
+
+   loader open /ext/apps/Tools/upython.fap
+
+You can also use the CLI to start the application and run a Python script:
+
+.. code-block:: shell
+
+   loader open /ext/apps/Tools/upython.fap /ext/scripts/tic_tac_toe.py
+
+Once the application is up an running, it registers the **py** command in the Flipper's CLI command registry.
+You can use this command to access the interactive MicroPython shell or start a script by passing the path as an argument:
+
+.. code-block:: shell
+
+   py /ext/scripts/tic_tac_toe.py
+
+Be aware, that the **py** command is only available as long as the application is running on the Flipper.
+Furthermore, you can only run one script at the time.

+ 90 - 7
docs/pages/reference.rst

@@ -233,8 +233,8 @@ Alignment
 .. autodata:: flipperzero.ALIGN_CENTER
 .. autodata:: flipperzero.ALIGN_CENTER
 .. autofunction:: flipperzero.canvas_set_text_align
 .. autofunction:: flipperzero.canvas_set_text_align
 
 
-Texts
-~~~~~
+Text
+~~~~
 
 
 .. autodata:: flipperzero.FONT_PRIMARY
 .. autodata:: flipperzero.FONT_PRIMARY
 .. autodata:: flipperzero.FONT_SECONDARY
 .. autodata:: flipperzero.FONT_SECONDARY
@@ -396,23 +396,106 @@ Functions
 .. autofunction:: flipperzero.infrared_transmit
 .. autofunction:: flipperzero.infrared_transmit
 .. autofunction:: flipperzero.infrared_is_busy
 .. autofunction:: flipperzero.infrared_is_busy
 
 
+UART
+----
+
+Connect to UART enabled devices.
+
+Modes
+~~~~~
+
+.. autodata:: flipperzero.UART_MODE_LPUART
+.. autodata:: flipperzero.UART_MODE_USART
+
+Functions
+~~~~~~~~~
+
+.. autofunction:: flipperzero.uart_open
+
+Classes
+~~~~~~~
+
+.. autoclass:: flipperzero.UART
+   :members: read, readline, readlines, write, flush, close, __enter__, __exit__, __del__
+
+Logging
+-------
+
+Log messages to the Flipper's own logging backend.
+Check out the `Flipper Zero docs <https://docs.flipper.net/development/cli#_yZ2E>`_ on how to reveal them in the CLI.
+Be aware, that you can't change Flipper's global log level from within your script.
+Change the `corresponding settings <https://docs.flipper.net/basics/settings#d5TAt>`_ instead or use the **log** command in the CLI with the desired log level as the first argument.
+
+Levels
+~~~~~~
+
+.. autodata:: logging.TRACE
+.. autodata:: logging.DEBUG
+.. autodata:: logging.INFO
+.. autodata:: logging.WARN
+.. autodata:: logging.ERROR
+.. autodata:: logging.NONE
+.. autodata:: logging.level
+
+Functions
+~~~~~~~~~
+
+.. autofunction:: logging.setLevel
+.. autofunction:: logging.getEffectiveLevel
+.. autofunction:: logging.trace
+.. autofunction:: logging.debug
+.. autofunction:: logging.info
+.. autofunction:: logging.warn
+.. autofunction:: logging.error
+.. autofunction:: logging.log
+
+I/O
+---
+
+Read and write files on the SD card.
+
+Constants
+~~~~~~~~~
+
+.. autodata:: io.SEEK_SET
+.. autodata:: io.SEEK_CUR
+.. autodata:: io.SEEK_END
+
+Functions
+~~~~~~~~~
+
+.. autofunction:: io.open
+
+Classes
+~~~~~~~
+
+.. autoclass:: io.BinaryFileIO
+   :members: name, read, readline, readlines, readable, writable, write, flush, seek, tell, close, __enter__, __exit__, __del__
+
+.. autoclass:: io.TextFileIO
+   :members: name, read, readline, readlines, readable, writable, write, flush, seek, tell, close, __enter__, __exit__, __del__
+
 Built-In
 Built-In
 --------
 --------
 
 
 The functions in this section are `not` part of the ``flipperzero`` module.
 The functions in this section are `not` part of the ``flipperzero`` module.
 They're members of the global namespace instead.
 They're members of the global namespace instead.
 
 
-.. py:function:: print(*objects, sep=' ', end='\n', file=None, flush=False) -> None
+.. py:function:: print(*objects, sep=' ', end='\n') -> None
 
 
    The standard Python `print <https://docs.python.org/3/library/functions.html#print>`_ function.
    The standard Python `print <https://docs.python.org/3/library/functions.html#print>`_ function.
+   Where the output of this function will be redirected depends on how the script is invoked:
+
+      * When invoked from the UI, the output will be sent to the Flipper's log buffer.
+        Check out the `Flipper Zero docs <https://docs.flipper.net/development/cli#_yZ2E>`_ on how to view them in the CLI interface.
+      * In the REPL, the output will be sent to the standard output buffer.
+      * When invoked by the **py** command, the output will be sent to the standard output buffer.
 
 
    :param objects: The objects to print (mostly a single string).
    :param objects: The objects to print (mostly a single string).
    :param sep: The separator to use between the objects.
    :param sep: The separator to use between the objects.
    :param end: The line terminator character to use.
    :param end: The line terminator character to use.
 
 
    .. versionadded:: 1.0.0
    .. versionadded:: 1.0.0
+   .. versionchanged:: 1.5.0
 
 
-   .. attention::
-      
-      This function prints to the internal log buffer.
-      Check out the `Flipper Zero docs <https://docs.flipper.net/development/cli#_yZ2E>`_ on how to reveal them in the CLI interface.
+      Output redirection, based on script invocation.

+ 1 - 0
docs/pages/roadmap.md

@@ -13,6 +13,7 @@ Here you can see what to expect from future releases.
 ## Planned
 ## Planned
 
 
 * I2C
 * I2C
+* NFC
 * UART
 * UART
 * USB HID
 * USB HID
 * Subghz
 * Subghz

+ 15 - 0
examples/logger.py

@@ -0,0 +1,15 @@
+import logging
+
+logging.setLevel(logging.TRACE)
+
+logging.trace('trace')
+logging.debug('debug %d %s',123,'message')
+logging.info('info %d %s',123,'message')
+logging.warn('warn %d %s',123,'message')
+logging.error('error %d %s',123,'message')
+
+logging.log(logging.TRACE, "level: %d", logging.TRACE)
+logging.log(logging.DEBUG, "level: %d", logging.DEBUG)
+logging.log(logging.INFO, "level: %d", logging.INFO)
+logging.log(logging.WARN, "level: %d", logging.WARN)
+logging.log(logging.ERROR, "level: %d", logging.ERROR)

+ 5 - 0
examples/open.py

@@ -0,0 +1,5 @@
+fp = open('/ext/spam.txt', 'w')
+
+fp.write('Some spam')
+
+fp.close()

+ 16 - 0
examples/uart.py

@@ -0,0 +1,16 @@
+import time
+import flipperzero as f0
+
+def read_from_uart():
+  with f0.uart_open(f0.UART_MODE_USART, 115200) as uart:
+    for _ in range(1000):
+      raw_data = uart.read()
+
+      if len(raw_data) > 0:
+        data = raw_data.decode()
+
+        print(data)
+
+      time.sleep_ms(10)
+
+read_from_uart()

+ 14 - 0
flipper.json

@@ -0,0 +1,14 @@
+{
+    "source": "examples",
+    "target": "/ext/examples",
+    "orphans": "ignore",
+    "include": [
+        "*.py"
+    ],
+    "exclude": [
+        "**.git**"
+    ],
+    "run": [
+        "loader open /ext/apps/Tools/upython.fap"
+    ]
+}

+ 164 - 0
flipperzero/_uart.py

@@ -0,0 +1,164 @@
+from typing import List
+
+UART_MODE_LPUART: int
+'''
+Constant value for the low power UART mode.
+
+.. versionadded:: 1.5.0
+'''
+
+UART_MODE_USART: int
+'''
+Constant value for the USART mode.
+
+.. versionadded:: 1.5.0
+'''
+
+class UART:
+    '''
+    This represents an UART connection.
+    The class has no :const:`__init__` method, use :func:`uart_open` to start an UART connection and receive an instance.
+
+    .. versionadded:: 1.5.0
+
+    An :class:`UART` instance is iterable:
+
+    .. code-block::
+
+        import flipperzero as f0
+
+        with f0.open(f0.UART_MODE_USART, 115200) as uart:
+            lines = [line for line in uart]
+    
+    An :class:`UART` instance can be used with a `context manager <https://docs.python.org/3/reference/datamodel.html#with-statement-context-managers>`_:
+
+    .. code-block::
+
+        import flipperzero as f0
+
+        with f0.open(f0.UART_MODE_USART, 115200) as uart:
+            ...
+
+    .. hint::
+
+        The read and write methods are non-blocking in terms of data availability.
+        They don't block code execution upon data is available.
+        Just an empty result will be returned.
+    '''
+
+    def read(self, size: int = -1) -> bytes:
+        '''
+        Read from the connection. 
+        The method will read up to ``size`` bytes and return them.
+        If ``size`` is not specified, all available data will be returned.
+        The method will return zero bytes, if no data is available.
+
+        :param size: The maximum number of bytes to read.
+        :returns: Up to ``size`` bytes.
+
+        .. versionadded:: 1.5.0
+        '''
+        pass
+
+    def readline(self, size: int = -1) -> bytes:
+        '''
+        Read and return one line from the connection.
+        If ``size`` is specified, at most ``size`` bytes will be read.
+        The line terminator is always ``b'\\n'``.
+
+        :param size: The maximum number of bytes to read.
+        :returns: Up to ``size`` bytes.
+
+        .. versionadded:: 1.5.0
+        '''
+        pass
+
+    def readlines(self) -> List[bytes]:
+        '''
+        Read and return a list of lines from the connection.
+        The line terminator is always ``b'\\n'``.
+
+        :returns: A list of bytes.
+
+        .. versionadded:: 1.5.0
+        '''
+        pass
+
+    def write(self, data: bytes) -> int:
+        '''
+        Write the given bytes to the connection stream.
+        The number of written bytes will be returned.
+        This can be less than the length of the provided data.
+        Be aware, that the data is not sent synchronously.
+        Call :meth:`flush` if you have to wait for the data to be sent.
+
+        :param data: The data to transmit.
+        :returns: The number of bytes sent.
+
+        .. versionadded:: 1.5.0
+        '''
+        pass
+
+    def flush(self) -> None:
+        '''
+        Flush the transmission buffer to the underlying UART connection.
+        This method blocks until all data is sent.
+
+        .. versionadded:: 1.5.0
+        '''
+        pass
+
+    def close(self) -> None:
+        '''
+        Close the UART connection.
+
+        .. versionadded:: 1.5.0
+        '''
+        pass
+
+    def __enter__(self) -> 'UART':
+        '''
+        This method is invoked, when the instance enters a runtime context.
+
+        :returns: The :class:`UART` connection.
+
+        .. versionadded:: 1.5.0
+        '''
+        pass
+
+    def __exit__(self, *args, **kwargs) -> None:
+        '''
+        This method is invoked, when the instance leavs a runtime context.
+        This basically calls :meth:`close` on the instance.
+
+        .. versionadded:: 1.5.0
+        '''
+        pass
+
+    def __del__(self) -> None:
+        '''
+        This method is invoked, when the garbage collector removes the object.
+        This basically calls :meth:`close` on the instance.
+
+        .. versionadded:: 1.5.0
+        '''
+        pass
+
+def uart_open(mode: int, baud_rate: int) -> UART:
+    '''
+    Open a connection to an UART enabled device by using the specified mode and baud rate.
+
+    :param mode: The mode to use, either :const:`UART_MODE_LPUART` or :const:`UART_MODE_USART`.
+    :param baud_rate: The baud rate to use.
+    :returns: A :class:`UART` object on success, :const:`None` otherwise.
+
+    .. versionadded:: 1.5.0
+
+    .. code-block::
+    
+        import flipperzero as f0
+        
+        with f0.uart_open(f0.UART_MODE_USART, 115200) as uart:
+            ...
+    '''
+    pass

+ 366 - 0
flipperzero/io.py

@@ -0,0 +1,366 @@
+import typing
+import io
+
+_open = io.open
+
+SEEK_SET: int = 0
+'''
+Set the pointer position relative to the beginning of the stream.
+
+.. versionadded:: 1.5.0
+'''
+
+SEEK_CUR: int = 1
+'''
+Set the pointer position relative to the current position.
+
+.. versionadded:: 1.5.0
+'''
+
+SEEK_END: int = 2
+'''
+Set the pointer position relative to the end of the stream.
+
+.. versionadded:: 1.5.0
+'''
+
+class BinaryFileIO:
+    '''
+    Represents a file, opened in binary mode.
+
+    .. versionadded:: 1.5.0
+    '''
+
+    name: str
+    '''
+    The name of the file.
+
+    .. versionadded:: 1.5.0
+    '''
+
+    readable: bool
+    '''
+    Read-only attribute, indicating if the file is readable.
+    
+    .. versionadded:: 1.5.0
+    '''
+
+    writable: bool
+    '''
+    Read-only attribute, indicating if the file is writable.
+    
+    .. versionadded:: 1.5.0
+    '''
+
+    def read(self, size: int = -1) -> bytes:
+        '''
+        Read from the file. 
+        The method will read up to ``size`` bytes and return them.
+        If ``size`` is not specified, all content up to EOF will be returned.
+        If the internal pointer is already at EOF, an empty byte string ``b''`` will be returned.
+
+        :param size: The maximum number of bytes to read.
+        :returns: Up to ``size`` bytes.
+
+        .. versionadded:: 1.5.0
+        '''
+        pass
+
+    def readline(self, size: int = -1) -> bytes:
+        '''
+        Read and return one line from the file.
+        If ``size`` is specified, at most ``size`` bytes will be read.
+        The line terminator is defined as ``b'\\n'``.
+        The new line character is included in the return value.
+        If the internal pointer is at EOF, an empty byte string ``b''`` will be returned.
+
+        :param size: The maximum number of bytes to read.
+        :returns: Up to ``size`` bytes.
+
+        .. versionadded:: 1.5.0
+        '''
+        pass
+
+    def readlines(self) -> typing.List[bytes]:
+        '''
+        Read and return a list of lines from the file.
+        The line terminator is defined as ``b'\\n'``.
+        The new line character is included in the return value.
+        If the internal pointer is at EOF, an empty list will be returned.
+
+        :returns: A list of bytes.
+
+        .. versionadded:: 1.5.0
+        '''
+        pass
+
+    def write(self, data: bytes) -> int:
+        '''
+        Write the given bytes to the file.
+        The number of written bytes will be returned.
+        This can be less than the length of the provided data.
+
+        :param data: The data to write.
+        :returns: The number of bytes written.
+
+        .. versionadded:: 1.5.0
+        '''
+        pass
+
+    def flush(self) -> None:
+        '''
+        Write the contents of the file buffer to the file on the SD card.
+
+        .. versionadded:: 1.5.0
+        '''
+        pass
+
+    def seek(self, offset: int, whence: int = SEEK_SET) -> int:
+        '''
+        Set the pointer position by the given ``offset``, relative to the position indicated by ``whence``.
+        The new absolute position will be returned.
+
+        :param offset: The offset to use.
+        :param whence: How to interpret the offset (e.g. :const:`SEEK_SET`).
+        :returns: The new absolute position.
+
+        .. versionadded:: 1.5.0
+        '''
+        pass
+
+    def tell(self) -> int:
+        '''
+        Get the current pointer position.
+
+        :returns: The absolute position of the pointer.
+
+        .. versionadded:: 1.5.0
+        '''
+        pass
+
+    def close(self) -> None:
+        '''
+        Close the file handle.
+
+        .. versionadded:: 1.5.0
+        '''
+        pass
+
+    def __enter__(self) -> 'BinaryFileIO':
+        '''
+        This method is invoked, when the instance enters a runtime context.
+
+        :returns: The :class:`BinaryFileIO` instance.
+
+        .. versionadded:: 1.5.0
+        '''
+        pass
+
+    def __exit__(self, *args, **kwargs) -> None:
+        '''
+        This method is invoked, when the instance leavs a runtime context.
+        This basically calls :meth:`close` on the instance.
+
+        .. versionadded:: 1.5.0
+        '''
+        pass
+
+    def __del__(self) -> None:
+        '''
+        This method is invoked, when the garbage collector removes the object.
+        This basically calls :meth:`close` on the instance.
+
+        .. versionadded:: 1.5.0
+        '''
+        pass
+
+class TextFileIO:
+    '''
+    Represents a file, opened in text mode.
+
+    .. versionadded:: 1.5.0
+    '''
+
+    name: str
+    '''
+    The name of the file.
+
+    .. versionadded:: 1.5.0
+    '''
+
+    readable: bool
+    '''
+    Read-only attribute, indicating if the file is readable.
+    
+    .. versionadded:: 1.5.0
+    '''
+
+    writable: bool
+    '''
+    Read-only attribute, indicating if the file is writable.
+    
+    .. versionadded:: 1.5.0
+    '''
+
+    def read(self, size: int = -1) -> str:
+        '''
+        Read from the file. 
+        The method will read up to ``size`` characters and return them.
+        If ``size`` is not specified, all content up to EOF will be returned.
+        If the internal pointer is already at EOF, an empty string will be returned.
+
+        :param size: The maximum number of characters to read.
+        :returns: Up to ``size`` characters.
+
+        .. versionadded:: 1.5.0
+        '''
+        pass
+
+    def readline(self, size: int = -1) -> str:
+        '''
+        Read and return one line from the file.
+        If ``size`` is specified, at most ``size`` characters will be read.
+        The line terminator is defined as ``'\\n'``.
+        The new line character is included in the return value.
+        If the internal pointer is at EOF, an empty string will be returned.
+
+        :param size: The maximum number of characters to read.
+        :returns: Up to ``size`` characters.
+
+        .. versionadded:: 1.5.0
+        '''
+        pass
+
+    def readlines(self) -> typing.List[str]:
+        '''
+        Read and return a list of lines from the file.
+        The line terminator is defined as ``'\\n'``.
+        The new line character is included in the return value.
+        If the internal pointer is at EOF, an empty list will be returned.
+
+        :returns: A list of strings.
+
+        .. versionadded:: 1.5.0
+        '''
+        pass
+
+    def write(self, data: str) -> int:
+        '''
+        Write the given string to the file.
+        The number of written characters will be returned.
+        This can be less than the length of the provided data.
+
+        :param data: The data to write.
+        :returns: The number of characters written.
+
+        .. versionadded:: 1.5.0
+        '''
+        pass
+
+    def flush(self) -> None:
+        '''
+        Write the contents of the file buffer to the file on the SD card.
+
+        .. versionadded:: 1.5.0
+        '''
+        pass
+
+    def seek(self, offset: int, whence: int = SEEK_SET) -> int:
+        '''
+        Set the pointer position by the given ``offset``, relative to the position indicated by ``whence``.
+        The new absolute position will be returned.
+
+        :param offset: The offset to use.
+        :param whence: How to interpret the offset (e.g. :const:`SEEK_SET`).
+        :returns: The new absolute position.
+
+        .. versionadded:: 1.5.0
+        '''
+        pass
+
+    def tell(self) -> int:
+        '''
+        Get the current pointer position.
+
+        :returns: The absolute position of the pointer.
+
+        .. versionadded:: 1.5.0
+        '''
+        pass
+
+    def close(self) -> None:
+        '''
+        Close the file handle.
+
+        .. versionadded:: 1.5.0
+        '''
+        pass
+
+    def __enter__(self) -> 'TextFileIO':
+        '''
+        This method is invoked, when the instance enters a runtime context.
+
+        :returns: The :class:`BinaryFileIO` instance.
+
+        .. versionadded:: 1.5.0
+        '''
+        pass
+
+    def __exit__(self, *args, **kwargs) -> None:
+        '''
+        This method is invoked, when the instance leavs a runtime context.
+        This basically calls :meth:`close` on the instance.
+
+        .. versionadded:: 1.5.0
+        '''
+        pass
+
+    def __del__(self) -> None:
+        '''
+        This method is invoked, when the garbage collector removes the object.
+        This basically calls :meth:`close` on the instance.
+
+        .. versionadded:: 1.5.0
+        '''
+        pass
+
+def open(path: str, mode: str, *args, **kwargs) -> BinaryFileIO | TextFileIO:
+    '''
+    Open a file on the file system with the specified mode.
+    The file path must always be absolute, beginning with ``/ext``.
+    The following modifiers are available:
+
+    .. list-table::
+        :header-rows: 1
+        :width: 90%
+
+        * - Character
+          - Description
+        * - ``'r'``
+          - Open for reading.
+            This is the default.
+            Will fail, if the file not exists.
+        * - ``'w'``
+          - Open for writing, truncating an existing file first.
+        * - ``'b'``
+          - Open the file in binary mode. 
+            The return value will be a :class:`BinaryFileIO` instance.
+        * - ``'t'``
+          - Open the in text mode.
+            This is the default.
+            The return value will be a :class:`TextFileIO` instance.
+        * - ``'+'``
+          - Open for reading and writing.
+            Will create the file, if it not exists.
+            The pointer will be placed at the end of the file.
+    
+    The modifiers can be combined, e.g. ``'rb+'`` would open a file for reading and writing in binary mode.
+
+    :param path: The path to the file to open.
+    :param mode: How the file should be opened.
+    :param args: Is ignored at the moment.
+    :param kwargs: Is ignored at the moment.
+
+    .. versionadded:: 1.5.0
+    '''
+    return io._open(path, mode, *args, **kwargs)

+ 162 - 0
flipperzero/logging.py

@@ -0,0 +1,162 @@
+import typing
+
+TRACE: int = 6
+'''
+Constant value for the `trace` log level.
+
+.. versionadded:: 1.5.0
+'''
+
+DEBUG: int = 5
+'''
+Constant value for the `debug` log level.
+
+.. versionadded:: 1.5.0
+'''
+
+INFO: int = 4
+'''
+Constant value for the `info` log level.
+
+.. versionadded:: 1.5.0
+'''
+
+WARN: int = 3
+'''
+Constant value for the `warn` log level.
+
+.. versionadded:: 1.5.0
+'''
+
+ERROR: int = 2
+'''
+Constant value for the `error` log level.
+
+.. versionadded:: 1.5.0
+'''
+
+NONE: int = 1
+'''
+Constant value for logging disabled.
+
+.. versionadded:: 1.5.0
+'''
+
+level: int
+'''
+The threshold log level, as set by the :func:`setLevel` function.
+The initial value is set to the :const:`INFO` level.
+
+.. versionadded:: 1.5.0
+
+.. hint::
+
+    Don't change the value of this variable, use :func:`setLevel` instead.
+'''
+
+def setLevel(level: int) -> None:
+    '''
+    Set the current log level of the application.
+
+    :param level: The log level to set (e.g. :const:`INFO`).
+
+    .. versionadded:: 1.5.0
+
+    .. hint::
+
+        This doesn't change the Flipper's effective log level settings.
+        Check out the Flipper's `documentation <https://docs.flipper.net/basics/settings#d5TAt>`_ for details on this topic.
+    '''
+    pass
+
+def getEffectiveLevel() -> int:
+    '''
+    Get the effective log level from the Flipper's settings.
+
+    :returns: The effective log level.
+
+    .. versionadded:: 1.5.0
+    '''
+    pass
+
+def trace(message: str, *args) -> None:
+    '''
+    Log a message with level :const:`TRACE`.
+    The ``message`` argument can be a format string with ``%`` placeholders.
+    No % formatting operation is performed when ``args`` is empty.
+
+    :param message: The message to log.
+    :param args: Values for the % formatting.
+
+    .. versionadded:: 1.5.0
+
+    .. code-block::
+
+        import logging
+
+        value = 42
+
+        logging.trace('value is %d', value)
+    '''
+    pass
+
+def debug(message: str, *args) -> None:
+    '''
+    Log a message with level :const:`DEBUG`.
+    See :func:`trace` for details on the usage.
+
+    :param message: The message to log.
+    :param args: Values for the % formatting.
+
+    .. versionadded:: 1.5.0
+    '''
+    pass
+
+def info(message: str, *args) -> None:
+    '''
+    Log a message with level :const:`INFO`.
+    See :func:`trace` for details on the usage.
+
+    :param message: The message to log.
+    :param args: Values for the % formatting.
+
+    .. versionadded:: 1.5.0
+    '''
+    pass
+
+def warn(message: str, *args) -> None:
+    '''
+    Log a message with level :const:`WARN`.
+    See :func:`trace` for details on the usage.
+
+    :param message: The message to log.
+    :param args: Values for the % formatting.
+
+    .. versionadded:: 1.5.0
+    '''
+    pass
+
+def error(message: str, *args) -> None:
+    '''
+    Log a message with level :const:`ERROR`.
+    See :func:`trace` for details on the usage.
+
+    :param message: The message to log.
+    :param args: Values for the % formatting.
+
+    .. versionadded:: 1.5.0
+    '''
+    pass
+
+def log(level: int, message: str, *args) -> None:
+    '''
+    Log a message with the given log level.
+    See :func:`trace` for details on the usage.
+
+    :param level: The log level to use (e.g. :const:`INFO`).
+    :param message: The message to log.
+    :param args: Values for the % formatting.
+
+    .. versionadded:: 1.5.0
+    '''
+    pass

+ 2 - 0
lib/micropython-port/mp_flipper_context.h

@@ -3,6 +3,7 @@
 #include <furi.h>
 #include <furi.h>
 #include <gui/gui.h>
 #include <gui/gui.h>
 #include <dialogs/dialogs.h>
 #include <dialogs/dialogs.h>
+#include <storage/storage.h>
 
 
 #include <mp_flipper_modflipperzero.h>
 #include <mp_flipper_modflipperzero.h>
 
 
@@ -40,6 +41,7 @@ typedef struct {
     const char* dialog_message_button_left;
     const char* dialog_message_button_left;
     const char* dialog_message_button_center;
     const char* dialog_message_button_center;
     const char* dialog_message_button_right;
     const char* dialog_message_button_right;
+    Storage* storage;
     FuriHalAdcHandle* adc_handle;
     FuriHalAdcHandle* adc_handle;
     mp_flipper_gpio_pin_t* gpio_pins;
     mp_flipper_gpio_pin_t* gpio_pins;
     mp_flipper_infrared_rx_t* infrared_rx;
     mp_flipper_infrared_rx_t* infrared_rx;

+ 9 - 6
lib/micropython-port/mp_flipper_file_helper.c

@@ -4,10 +4,15 @@
 #include <furi.h>
 #include <furi.h>
 #include <storage/storage.h>
 #include <storage/storage.h>
 
 
+#include <py/mperrno.h>
+
+#include "mp_flipper_context.h"
+
 mp_flipper_import_stat_t mp_flipper_try_resolve_filesystem_path(FuriString* path) {
 mp_flipper_import_stat_t mp_flipper_try_resolve_filesystem_path(FuriString* path) {
+    mp_flipper_context_t* ctx = mp_flipper_context;
+
     const char* path_str = furi_string_get_cstr(path);
     const char* path_str = furi_string_get_cstr(path);
     FuriString* _path = furi_string_alloc_printf("%s", path_str);
     FuriString* _path = furi_string_alloc_printf("%s", path_str);
-    Storage* storage = furi_record_open(RECORD_STORAGE);
 
 
     mp_flipper_import_stat_t stat = MP_FLIPPER_IMPORT_STAT_FILE;
     mp_flipper_import_stat_t stat = MP_FLIPPER_IMPORT_STAT_FILE;
 
 
@@ -21,7 +26,7 @@ mp_flipper_import_stat_t mp_flipper_try_resolve_filesystem_path(FuriString* path
         }
         }
 
 
         // check if file or folder exists
         // check if file or folder exists
-        error = storage_common_stat(storage, furi_string_get_cstr(_path), &info);
+        error = storage_common_stat(ctx->storage, furi_string_get_cstr(_path), &info);
         if(error == FSE_OK) {
         if(error == FSE_OK) {
             break;
             break;
         }
         }
@@ -29,7 +34,7 @@ mp_flipper_import_stat_t mp_flipper_try_resolve_filesystem_path(FuriString* path
         // check for existing python file
         // check for existing python file
         furi_string_cat_str(_path, ".py");
         furi_string_cat_str(_path, ".py");
 
 
-        error = storage_common_stat(storage, furi_string_get_cstr(_path), &info);
+        error = storage_common_stat(ctx->storage, furi_string_get_cstr(_path), &info);
         if(error == FSE_OK) {
         if(error == FSE_OK) {
             break;
             break;
         }
         }
@@ -48,9 +53,7 @@ mp_flipper_import_stat_t mp_flipper_try_resolve_filesystem_path(FuriString* path
         stat = MP_FLIPPER_IMPORT_STAT_DIR;
         stat = MP_FLIPPER_IMPORT_STAT_DIR;
     }
     }
 
 
-    furi_record_close(RECORD_STORAGE);
-
     furi_string_move(path, _path);
     furi_string_move(path, _path);
 
 
     return stat;
     return stat;
-}
+}

+ 23 - 22
lib/micropython-port/mp_flipper_file_reader.c

@@ -3,74 +3,75 @@
 #include <furi.h>
 #include <furi.h>
 #include <storage/storage.h>
 #include <storage/storage.h>
 
 
+#include <py/mperrno.h>
+
 #include <mp_flipper_runtime.h>
 #include <mp_flipper_runtime.h>
 #include <mp_flipper_file_reader.h>
 #include <mp_flipper_file_reader.h>
 
 
+#include "mp_flipper_context.h"
 #include "mp_flipper_file_helper.h"
 #include "mp_flipper_file_helper.h"
 
 
 typedef struct {
 typedef struct {
     size_t pointer;
     size_t pointer;
     FuriString* content;
     FuriString* content;
     size_t size;
     size_t size;
-} FileReaderContext;
+} FileDescriptor;
 
 
 inline void* mp_flipper_file_reader_context_alloc(const char* filename) {
 inline void* mp_flipper_file_reader_context_alloc(const char* filename) {
+    mp_flipper_context_t* ctx = mp_flipper_context;
+
     FuriString* path = furi_string_alloc_printf("%s", filename);
     FuriString* path = furi_string_alloc_printf("%s", filename);
-    Storage* storage = furi_record_open(RECORD_STORAGE);
-    File* file = storage_file_alloc(storage);
-    FileReaderContext* ctx = NULL;
+    File* file = storage_file_alloc(ctx->storage);
+    FileDescriptor* fd = NULL;
 
 
     do {
     do {
         if(mp_flipper_try_resolve_filesystem_path(path) == MP_FLIPPER_IMPORT_STAT_NO_EXIST) {
         if(mp_flipper_try_resolve_filesystem_path(path) == MP_FLIPPER_IMPORT_STAT_NO_EXIST) {
-            furi_string_free(path);
-
             mp_flipper_raise_os_error_with_filename(MP_ENOENT, filename);
             mp_flipper_raise_os_error_with_filename(MP_ENOENT, filename);
+
+            break;
         }
         }
 
 
         if(!storage_file_open(file, furi_string_get_cstr(path), FSAM_READ, FSOM_OPEN_EXISTING)) {
         if(!storage_file_open(file, furi_string_get_cstr(path), FSAM_READ, FSOM_OPEN_EXISTING)) {
-            storage_file_free(file);
-
             mp_flipper_raise_os_error_with_filename(MP_ENOENT, filename);
             mp_flipper_raise_os_error_with_filename(MP_ENOENT, filename);
 
 
             break;
             break;
         }
         }
 
 
-        ctx = malloc(sizeof(FileReaderContext));
+        fd = malloc(sizeof(FileDescriptor));
 
 
-        ctx->pointer = 0;
-        ctx->content = furi_string_alloc();
-        ctx->size = storage_file_size(file);
+        fd->pointer = 0;
+        fd->content = furi_string_alloc();
+        fd->size = storage_file_size(file);
 
 
         char character = '\0';
         char character = '\0';
 
 
-        for(size_t i = 0; i < ctx->size; i++) {
+        for(size_t i = 0; i < fd->size; i++) {
             storage_file_read(file, &character, 1);
             storage_file_read(file, &character, 1);
 
 
-            furi_string_push_back(ctx->content, character);
+            furi_string_push_back(fd->content, character);
         }
         }
     } while(false);
     } while(false);
 
 
     storage_file_free(file);
     storage_file_free(file);
-    furi_record_close(RECORD_STORAGE);
     furi_string_free(path);
     furi_string_free(path);
 
 
-    return ctx;
+    return fd;
 }
 }
 
 
 inline uint32_t mp_flipper_file_reader_read(void* data) {
 inline uint32_t mp_flipper_file_reader_read(void* data) {
-    FileReaderContext* ctx = data;
+    FileDescriptor* fd = data;
 
 
-    if(ctx->pointer >= ctx->size) {
+    if(fd->pointer >= fd->size) {
         return MP_FLIPPER_FILE_READER_EOF;
         return MP_FLIPPER_FILE_READER_EOF;
     }
     }
 
 
-    return furi_string_get_char(ctx->content, ctx->pointer++);
+    return furi_string_get_char(fd->content, fd->pointer++);
 }
 }
 
 
 void mp_flipper_file_reader_close(void* data) {
 void mp_flipper_file_reader_close(void* data) {
-    FileReaderContext* ctx = data;
+    FileDescriptor* fd = data;
 
 
-    furi_string_free(ctx->content);
+    furi_string_free(fd->content);
 
 
     free(data);
     free(data);
-}
+}

+ 89 - 0
lib/micropython-port/mp_flipper_fileio.c

@@ -0,0 +1,89 @@
+#include <furi.h>
+#include <storage/storage.h>
+
+#include <mp_flipper_fileio.h>
+#include <mp_flipper_runtime.h>
+
+#include "mp_flipper_context.h"
+#include "mp_flipper_file_helper.h"
+
+uint8_t MP_FLIPPER_FILE_ACCESS_MODE_READ = FSAM_READ;
+uint8_t MP_FLIPPER_FILE_ACCESS_MODE_WRITE = FSAM_WRITE;
+
+uint8_t MP_FLIPPER_FILE_OPEN_MODE_OPEN_EXIST = FSOM_OPEN_EXISTING;
+uint8_t MP_FLIPPER_FILE_OPEN_MODE_OPEN_ALWAYS = FSOM_OPEN_ALWAYS;
+uint8_t MP_FLIPPER_FILE_OPEN_MODE_OPEN_APPEND = FSOM_OPEN_APPEND;
+uint8_t MP_FLIPPER_FILE_OPEN_MODE_CREATE_NEW = FSOM_CREATE_NEW;
+uint8_t MP_FLIPPER_FILE_OPEN_MODE_CREATE_ALWAYS = FSOM_CREATE_ALWAYS;
+
+inline void* mp_flipper_file_open(const char* name, uint8_t access_mode, uint8_t open_mode) {
+    mp_flipper_context_t* ctx = mp_flipper_context;
+
+    File* file = storage_file_alloc(ctx->storage);
+    FuriString* path = furi_string_alloc_set_str(name);
+
+    do {
+        if(mp_flipper_try_resolve_filesystem_path(path) == MP_FLIPPER_IMPORT_STAT_NO_EXIST) {
+            break;
+        }
+
+        if(!storage_file_open(file, furi_string_get_cstr(path), access_mode, open_mode)) {
+            break;
+        }
+    } while(false);
+
+    if(!storage_file_is_open(file)) {
+        storage_file_close(file);
+        storage_file_free(file);
+
+        return NULL;
+    }
+
+    return file;
+}
+
+inline bool mp_flipper_file_close(void* handle) {
+    File* file = handle;
+
+    bool success = storage_file_is_open(file) && storage_file_close(file);
+
+    storage_file_free(file);
+
+    return success;
+}
+
+inline size_t mp_flipper_file_seek(void* handle, uint32_t offset) {
+    return storage_file_seek(handle, offset, true);
+}
+
+inline size_t mp_flipper_file_tell(void* handle) {
+    return storage_file_tell(handle);
+}
+
+inline size_t mp_flipper_file_size(void* handle) {
+    return storage_file_size(handle);
+}
+
+inline bool mp_flipper_file_sync(void* handle) {
+    return storage_file_sync(handle);
+}
+
+inline bool mp_flipper_file_eof(void* handle) {
+    return storage_file_eof(handle);
+}
+
+inline size_t mp_flipper_file_read(void* handle, void* buffer, size_t size, int* errcode) {
+    File* file = handle;
+
+    *errcode = 0; // TODO handle error
+
+    return storage_file_read(file, buffer, size);
+}
+
+inline size_t mp_flipper_file_write(void* handle, const void* buffer, size_t size, int* errcode) {
+    File* file = handle;
+
+    *errcode = 0; // TODO handle error
+
+    return storage_file_write(file, buffer, size);
+}

+ 2 - 2
lib/micropython-port/mp_flipper_halport.c

@@ -9,11 +9,11 @@
 #include "mp_flipper_file_helper.h"
 #include "mp_flipper_file_helper.h"
 
 
 inline void mp_flipper_stdout_tx_str(const char* str) {
 inline void mp_flipper_stdout_tx_str(const char* str) {
-    printf("%s", str);
+    furi_thread_stdout_write(str, strlen(str));
 }
 }
 
 
 inline void mp_flipper_stdout_tx_strn_cooked(const char* str, size_t len) {
 inline void mp_flipper_stdout_tx_strn_cooked(const char* str, size_t len) {
-    printf("%.*s", len, str);
+    furi_thread_stdout_write(str, len);
 }
 }
 
 
 inline mp_flipper_import_stat_t mp_flipper_import_stat(const char* path) {
 inline mp_flipper_import_stat_t mp_flipper_import_stat(const char* path) {

+ 50 - 0
lib/micropython-port/mp_flipper_logging.c

@@ -0,0 +1,50 @@
+#include <furi.h>
+
+#include <mp_flipper_logging.h>
+#include <mp_flipper_runtime.h>
+
+#include "mp_flipper_context.h"
+
+static inline FuriLogLevel decode_log_level(uint8_t level) {
+    switch(level) {
+    case MP_FLIPPER_LOG_LEVEL_TRACE:
+        return FuriLogLevelTrace;
+    case MP_FLIPPER_LOG_LEVEL_DEBUG:
+        return FuriLogLevelDebug;
+    case MP_FLIPPER_LOG_LEVEL_INFO:
+        return FuriLogLevelInfo;
+    case MP_FLIPPER_LOG_LEVEL_WARN:
+        return FuriLogLevelWarn;
+    case MP_FLIPPER_LOG_LEVEL_ERROR:
+        return FuriLogLevelError;
+    case MP_FLIPPER_LOG_LEVEL_NONE:
+        return FuriLogLevelNone;
+    default:
+        return FuriLogLevelNone;
+    }
+}
+
+inline uint8_t mp_flipper_log_get_effective_level() {
+    switch(furi_log_get_level()) {
+    case FuriLogLevelTrace:
+        return MP_FLIPPER_LOG_LEVEL_TRACE;
+    case FuriLogLevelDebug:
+        return MP_FLIPPER_LOG_LEVEL_DEBUG;
+    case FuriLogLevelInfo:
+        return MP_FLIPPER_LOG_LEVEL_INFO;
+    case FuriLogLevelWarn:
+        return MP_FLIPPER_LOG_LEVEL_WARN;
+    case FuriLogLevelError:
+        return MP_FLIPPER_LOG_LEVEL_ERROR;
+    case FuriLogLevelNone:
+        return MP_FLIPPER_LOG_LEVEL_NONE;
+    default:
+        return MP_FLIPPER_LOG_LEVEL_NONE;
+    }
+}
+
+inline void mp_flipper_log(uint8_t raw_level, const char* message) {
+    FuriLogLevel level = decode_log_level(raw_level);
+
+    furi_log_print_format(level, "uPython", message);
+}

+ 81 - 0
lib/micropython-port/mp_flipper_modflipperzero_uart.c

@@ -0,0 +1,81 @@
+#include <furi.h>
+#include <furi_hal.h>
+
+#include <mp_flipper_modflipperzero.h>
+
+typedef struct {
+    FuriHalSerialHandle* handle;
+    FuriStreamBuffer* rx_buffer;
+} mp_flipper_uart_ctx_t;
+
+static void on_uart_rx(FuriHalSerialHandle* handle, FuriHalSerialRxEvent event, void* context) {
+    FuriStreamBuffer* buffer = context;
+
+    if(event & FuriHalSerialRxEventData) {
+        uint8_t data = furi_hal_serial_async_rx(handle);
+        furi_stream_buffer_send(buffer, &data, 1, 0);
+    }
+}
+
+inline void* mp_flipper_uart_open(uint8_t raw_mode, uint32_t baud_rate) {
+    FuriHalSerialId mode = raw_mode == MP_FLIPPER_UART_MODE_USART ? FuriHalSerialIdUsart :
+                                                                    FuriHalSerialIdLpuart;
+
+    if(furi_hal_serial_control_is_busy(mode)) {
+        return NULL;
+    }
+
+    mp_flipper_uart_ctx_t* ctx = malloc(sizeof(mp_flipper_uart_ctx_t));
+
+    ctx->handle = furi_hal_serial_control_acquire(mode);
+    ctx->rx_buffer = furi_stream_buffer_alloc(512, 1);
+
+    furi_hal_serial_init(ctx->handle, baud_rate);
+
+    furi_hal_serial_async_rx_start(ctx->handle, on_uart_rx, ctx->rx_buffer, false);
+
+    return ctx;
+}
+
+inline bool mp_flipper_uart_close(void* handle) {
+    mp_flipper_uart_ctx_t* ctx = handle;
+
+    furi_hal_serial_deinit(ctx->handle);
+    furi_hal_serial_control_release(ctx->handle);
+
+    furi_stream_buffer_free(ctx->rx_buffer);
+
+    free(ctx);
+
+    return true;
+}
+
+inline bool mp_flipper_uart_sync(void* handle) {
+    mp_flipper_uart_ctx_t* ctx = handle;
+
+    furi_hal_serial_tx_wait_complete(ctx->handle);
+}
+
+inline size_t mp_flipper_uart_read(void* handle, void* buffer, size_t size, int* errcode) {
+    mp_flipper_uart_ctx_t* ctx = handle;
+
+    size_t read = 0;
+    size_t total = 0;
+    size_t left = size;
+
+    do {
+        read = furi_stream_buffer_receive(ctx->rx_buffer, &buffer[read], left, 0);
+        total += read;
+        left -= read;
+    } while(read > 0);
+
+    return total;
+}
+
+inline size_t mp_flipper_uart_write(void* handle, const void* buffer, size_t size, int* errcode) {
+    mp_flipper_uart_ctx_t* ctx = handle;
+
+    furi_hal_serial_tx(ctx->handle, buffer, size);
+
+    return size;
+}

+ 15 - 3
lib/micropython-port/mp_flipper_polyfill.c

@@ -4,11 +4,23 @@
 #include <math.h>
 #include <math.h>
 #include <string.h>
 #include <string.h>
 
 
-#define POLYFILL_FUN_1(name, ret, arg1) ret name(arg1)
-#define POLYFILL_FUN_2(name, ret, arg1, arg2) ret name(arg1, arg2)
-#define POLYFILL_FUN_3(name, ret, arg1, arg2, arg3) ret name(arg1, arg2, arg3)
+#define POLYFILL_FUN_1(name, ret, arg1)                   ret name(arg1)
+#define POLYFILL_FUN_2(name, ret, arg1, arg2)             ret name(arg1, arg2)
+#define POLYFILL_FUN_3(name, ret, arg1, arg2, arg3)       ret name(arg1, arg2, arg3)
 #define POLYFILL_FUN_4(name, ret, arg1, arg2, arg3, arg4) ret name(arg1, arg2, arg3, arg4)
 #define POLYFILL_FUN_4(name, ret, arg1, arg2, arg3, arg4) ret name(arg1, arg2, arg3, arg4)
 
 
+#ifndef __aeabi_l2f
+POLYFILL_FUN_1(__aeabi_l2f, float, long long x) {
+    return x;
+}
+#endif
+
+#ifndef __aeabi_f2lz
+POLYFILL_FUN_1(__aeabi_f2lz, long long, float x) {
+    return x;
+}
+#endif
+
 #ifndef __aeabi_dcmple
 #ifndef __aeabi_dcmple
 POLYFILL_FUN_2(__aeabi_dcmple, double, double, double) {
 POLYFILL_FUN_2(__aeabi_dcmple, double, double, double) {
 }
 }

+ 9 - 3
lib/micropython-port/mp_flipper_runtime.c

@@ -1,6 +1,8 @@
 #include <furi.h>
 #include <furi.h>
 #include <storage/storage.h>
 #include <storage/storage.h>
 
 
+#include <py/mperrno.h>
+
 #include <mp_flipper_runtime.h>
 #include <mp_flipper_runtime.h>
 #include <mp_flipper_modflipperzero.h>
 #include <mp_flipper_modflipperzero.h>
 
 
@@ -14,8 +16,9 @@ static void on_input_callback(const InputEvent* event, void* ctx) {
 }
 }
 
 
 void mp_flipper_save_file(const char* file_path, const char* data, size_t size) {
 void mp_flipper_save_file(const char* file_path, const char* data, size_t size) {
-    Storage* storage = furi_record_open(RECORD_STORAGE);
-    File* file = storage_file_alloc(storage);
+    mp_flipper_context_t* ctx = mp_flipper_context;
+
+    File* file = storage_file_alloc(ctx->storage);
 
 
     do {
     do {
         if(!storage_file_open(file, file_path, FSAM_WRITE, FSOM_CREATE_ALWAYS)) {
         if(!storage_file_open(file, file_path, FSAM_WRITE, FSOM_CREATE_ALWAYS)) {
@@ -30,7 +33,6 @@ void mp_flipper_save_file(const char* file_path, const char* data, size_t size)
     } while(false);
     } while(false);
 
 
     storage_file_free(file);
     storage_file_free(file);
-    furi_record_close(RECORD_STORAGE);
 }
 }
 
 
 inline void mp_flipper_nlr_jump_fail(void* val) {
 inline void mp_flipper_nlr_jump_fail(void* val) {
@@ -84,6 +86,8 @@ void* mp_flipper_context_alloc() {
     ctx->dialog_message_button_center = NULL;
     ctx->dialog_message_button_center = NULL;
     ctx->dialog_message_button_right = NULL;
     ctx->dialog_message_button_right = NULL;
 
 
+    ctx->storage = furi_record_open(RECORD_STORAGE);
+
     ctx->adc_handle = NULL;
     ctx->adc_handle = NULL;
 
 
     // GPIO
     // GPIO
@@ -129,6 +133,8 @@ void mp_flipper_context_free(void* context) {
     furi_record_close(RECORD_GUI);
     furi_record_close(RECORD_GUI);
     furi_record_close(RECORD_INPUT_EVENTS);
     furi_record_close(RECORD_INPUT_EVENTS);
 
 
+    furi_record_close(RECORD_STORAGE);
+
     // disable ADC handle
     // disable ADC handle
     if(ctx->adc_handle) {
     if(ctx->adc_handle) {
         furi_hal_adc_release(ctx->adc_handle);
         furi_hal_adc_release(ctx->adc_handle);

+ 10 - 0
lib/micropython/genhdr/moduledefs.h

@@ -1,5 +1,13 @@
 // Automatically generated by makemoduledefs.py.
 // Automatically generated by makemoduledefs.py.
 
 
+extern const struct _mp_obj_module_t mp_module_io;
+#undef MODULE_DEF_IO
+#define MODULE_DEF_IO { MP_ROM_QSTR(MP_QSTR_io), MP_ROM_PTR(&mp_module_io) },
+
+extern const struct _mp_obj_module_t mp_module_logging;
+#undef MODULE_DEF_LOGGING
+#define MODULE_DEF_LOGGING { MP_ROM_QSTR(MP_QSTR_logging), MP_ROM_PTR(&mp_module_logging) },
+
 extern const struct _mp_obj_module_t mp_module_random;
 extern const struct _mp_obj_module_t mp_module_random;
 #undef MODULE_DEF_RANDOM
 #undef MODULE_DEF_RANDOM
 #define MODULE_DEF_RANDOM { MP_ROM_QSTR(MP_QSTR_random), MP_ROM_PTR(&mp_module_random) },
 #define MODULE_DEF_RANDOM { MP_ROM_QSTR(MP_QSTR_random), MP_ROM_PTR(&mp_module_random) },
@@ -28,6 +36,8 @@ extern const struct _mp_obj_module_t flipperzero_module;
 // MICROPY_REGISTERED_MODULES
 // MICROPY_REGISTERED_MODULES
 
 
 #define MICROPY_REGISTERED_EXTENSIBLE_MODULES \
 #define MICROPY_REGISTERED_EXTENSIBLE_MODULES \
+    MODULE_DEF_IO \
+    MODULE_DEF_LOGGING \
     MODULE_DEF_RANDOM \
     MODULE_DEF_RANDOM \
     MODULE_DEF_TIME \
     MODULE_DEF_TIME \
 // MICROPY_REGISTERED_EXTENSIBLE_MODULES
 // MICROPY_REGISTERED_EXTENSIBLE_MODULES

+ 3 - 3
lib/micropython/genhdr/mpversion.h

@@ -1,4 +1,4 @@
 // This file was generated by py/makeversionhdr.py
 // This file was generated by py/makeversionhdr.py
-#define MICROPY_GIT_TAG "v1.23.0-preview.322.g5114f2c1e"
-#define MICROPY_GIT_HASH "5114f2c1e"
-#define MICROPY_BUILD_DATE "2024-09-08"
+#define MICROPY_GIT_TAG "v1.23.0"
+#define MICROPY_GIT_HASH "a61c446c0"
+#define MICROPY_BUILD_DATE "2024-10-06"

+ 36 - 2
lib/micropython/genhdr/qstrdefs.generated.h

@@ -166,6 +166,8 @@ QDEF0(MP_QSTR_value, 78, 5, "value")
 QDEF0(MP_QSTR_values, 125, 6, "values")
 QDEF0(MP_QSTR_values, 125, 6, "values")
 QDEF0(MP_QSTR_write, 152, 5, "write")
 QDEF0(MP_QSTR_write, 152, 5, "write")
 QDEF0(MP_QSTR_zip, 230, 3, "zip")
 QDEF0(MP_QSTR_zip, 230, 3, "zip")
+QDEF1(MP_QSTR__percent__hash_o, 108, 3, "%#o")
+QDEF1(MP_QSTR__percent__hash_x, 123, 3, "%#x")
 QDEF0(MP_QSTR__lt_dictcomp_gt_, 204, 10, "<dictcomp>")
 QDEF0(MP_QSTR__lt_dictcomp_gt_, 204, 10, "<dictcomp>")
 QDEF0(MP_QSTR__lt_genexpr_gt_, 52, 9, "<genexpr>")
 QDEF0(MP_QSTR__lt_genexpr_gt_, 52, 9, "<genexpr>")
 QDEF0(MP_QSTR__lt_lambda_gt_, 128, 8, "<lambda>")
 QDEF0(MP_QSTR__lt_lambda_gt_, 128, 8, "<lambda>")
@@ -176,8 +178,11 @@ QDEF1(MP_QSTR__lt_string_gt_, 82, 8, "<string>")
 QDEF1(MP_QSTR_ALIGN_BEGIN, 240, 11, "ALIGN_BEGIN")
 QDEF1(MP_QSTR_ALIGN_BEGIN, 240, 11, "ALIGN_BEGIN")
 QDEF1(MP_QSTR_ALIGN_CENTER, 28, 12, "ALIGN_CENTER")
 QDEF1(MP_QSTR_ALIGN_CENTER, 28, 12, "ALIGN_CENTER")
 QDEF1(MP_QSTR_ALIGN_END, 248, 9, "ALIGN_END")
 QDEF1(MP_QSTR_ALIGN_END, 248, 9, "ALIGN_END")
+QDEF1(MP_QSTR_BinaryFileIO, 106, 12, "BinaryFileIO")
 QDEF1(MP_QSTR_CANVAS_BLACK, 213, 12, "CANVAS_BLACK")
 QDEF1(MP_QSTR_CANVAS_BLACK, 213, 12, "CANVAS_BLACK")
 QDEF1(MP_QSTR_CANVAS_WHITE, 53, 12, "CANVAS_WHITE")
 QDEF1(MP_QSTR_CANVAS_WHITE, 53, 12, "CANVAS_WHITE")
+QDEF1(MP_QSTR_DEBUG, 52, 5, "DEBUG")
+QDEF1(MP_QSTR_ERROR, 157, 5, "ERROR")
 QDEF1(MP_QSTR_FONT_PRIMARY, 133, 12, "FONT_PRIMARY")
 QDEF1(MP_QSTR_FONT_PRIMARY, 133, 12, "FONT_PRIMARY")
 QDEF1(MP_QSTR_FONT_SECONDARY, 51, 14, "FONT_SECONDARY")
 QDEF1(MP_QSTR_FONT_SECONDARY, 51, 14, "FONT_SECONDARY")
 QDEF1(MP_QSTR_GPIO_MODE_ANALOG, 29, 16, "GPIO_MODE_ANALOG")
 QDEF1(MP_QSTR_GPIO_MODE_ANALOG, 29, 16, "GPIO_MODE_ANALOG")
@@ -201,6 +206,7 @@ QDEF1(MP_QSTR_GPIO_SPEED_HIGH, 125, 15, "GPIO_SPEED_HIGH")
 QDEF1(MP_QSTR_GPIO_SPEED_LOW, 71, 14, "GPIO_SPEED_LOW")
 QDEF1(MP_QSTR_GPIO_SPEED_LOW, 71, 14, "GPIO_SPEED_LOW")
 QDEF1(MP_QSTR_GPIO_SPEED_MEDIUM, 78, 17, "GPIO_SPEED_MEDIUM")
 QDEF1(MP_QSTR_GPIO_SPEED_MEDIUM, 78, 17, "GPIO_SPEED_MEDIUM")
 QDEF1(MP_QSTR_GPIO_SPEED_VERY_HIGH, 154, 20, "GPIO_SPEED_VERY_HIGH")
 QDEF1(MP_QSTR_GPIO_SPEED_VERY_HIGH, 154, 20, "GPIO_SPEED_VERY_HIGH")
+QDEF1(MP_QSTR_INFO, 235, 4, "INFO")
 QDEF1(MP_QSTR_INPUT_BUTTON_BACK, 174, 17, "INPUT_BUTTON_BACK")
 QDEF1(MP_QSTR_INPUT_BUTTON_BACK, 174, 17, "INPUT_BUTTON_BACK")
 QDEF1(MP_QSTR_INPUT_BUTTON_DOWN, 247, 17, "INPUT_BUTTON_DOWN")
 QDEF1(MP_QSTR_INPUT_BUTTON_DOWN, 247, 17, "INPUT_BUTTON_DOWN")
 QDEF1(MP_QSTR_INPUT_BUTTON_LEFT, 94, 17, "INPUT_BUTTON_LEFT")
 QDEF1(MP_QSTR_INPUT_BUTTON_LEFT, 94, 17, "INPUT_BUTTON_LEFT")
@@ -216,6 +222,10 @@ QDEF1(MP_QSTR_LIGHT_BACKLIGHT, 17, 15, "LIGHT_BACKLIGHT")
 QDEF1(MP_QSTR_LIGHT_BLUE, 90, 10, "LIGHT_BLUE")
 QDEF1(MP_QSTR_LIGHT_BLUE, 90, 10, "LIGHT_BLUE")
 QDEF1(MP_QSTR_LIGHT_GREEN, 95, 11, "LIGHT_GREEN")
 QDEF1(MP_QSTR_LIGHT_GREEN, 95, 11, "LIGHT_GREEN")
 QDEF1(MP_QSTR_LIGHT_RED, 215, 9, "LIGHT_RED")
 QDEF1(MP_QSTR_LIGHT_RED, 215, 9, "LIGHT_RED")
+QDEF1(MP_QSTR_NONE, 79, 4, "NONE")
+QDEF1(MP_QSTR_SEEK_CUR, 134, 8, "SEEK_CUR")
+QDEF1(MP_QSTR_SEEK_END, 237, 8, "SEEK_END")
+QDEF1(MP_QSTR_SEEK_SET, 128, 8, "SEEK_SET")
 QDEF1(MP_QSTR_SPEAKER_NOTE_A0, 95, 15, "SPEAKER_NOTE_A0")
 QDEF1(MP_QSTR_SPEAKER_NOTE_A0, 95, 15, "SPEAKER_NOTE_A0")
 QDEF1(MP_QSTR_SPEAKER_NOTE_A1, 94, 15, "SPEAKER_NOTE_A1")
 QDEF1(MP_QSTR_SPEAKER_NOTE_A1, 94, 15, "SPEAKER_NOTE_A1")
 QDEF1(MP_QSTR_SPEAKER_NOTE_A2, 93, 15, "SPEAKER_NOTE_A2")
 QDEF1(MP_QSTR_SPEAKER_NOTE_A2, 93, 15, "SPEAKER_NOTE_A2")
@@ -326,11 +336,18 @@ QDEF1(MP_QSTR_SPEAKER_NOTE_GS7, 13, 16, "SPEAKER_NOTE_GS7")
 QDEF1(MP_QSTR_SPEAKER_NOTE_GS8, 2, 16, "SPEAKER_NOTE_GS8")
 QDEF1(MP_QSTR_SPEAKER_NOTE_GS8, 2, 16, "SPEAKER_NOTE_GS8")
 QDEF1(MP_QSTR_SPEAKER_VOLUME_MAX, 66, 18, "SPEAKER_VOLUME_MAX")
 QDEF1(MP_QSTR_SPEAKER_VOLUME_MAX, 66, 18, "SPEAKER_VOLUME_MAX")
 QDEF1(MP_QSTR_SPEAKER_VOLUME_MIN, 92, 18, "SPEAKER_VOLUME_MIN")
 QDEF1(MP_QSTR_SPEAKER_VOLUME_MIN, 92, 18, "SPEAKER_VOLUME_MIN")
+QDEF1(MP_QSTR_TRACE, 196, 5, "TRACE")
+QDEF1(MP_QSTR_TextFileIO, 56, 10, "TextFileIO")
+QDEF1(MP_QSTR_UART, 183, 4, "UART")
+QDEF1(MP_QSTR_UART_MODE_LPUART, 250, 16, "UART_MODE_LPUART")
+QDEF1(MP_QSTR_UART_MODE_USART, 53, 15, "UART_MODE_USART")
+QDEF1(MP_QSTR_WARN, 239, 4, "WARN")
 QDEF0(MP_QSTR___add__, 196, 7, "__add__")
 QDEF0(MP_QSTR___add__, 196, 7, "__add__")
 QDEF1(MP_QSTR___bases__, 3, 9, "__bases__")
 QDEF1(MP_QSTR___bases__, 3, 9, "__bases__")
 QDEF0(MP_QSTR___bool__, 43, 8, "__bool__")
 QDEF0(MP_QSTR___bool__, 43, 8, "__bool__")
 QDEF1(MP_QSTR___build_class__, 66, 15, "__build_class__")
 QDEF1(MP_QSTR___build_class__, 66, 15, "__build_class__")
 QDEF0(MP_QSTR___contains__, 198, 12, "__contains__")
 QDEF0(MP_QSTR___contains__, 198, 12, "__contains__")
+QDEF1(MP_QSTR___del__, 104, 7, "__del__")
 QDEF1(MP_QSTR___dict__, 127, 8, "__dict__")
 QDEF1(MP_QSTR___dict__, 127, 8, "__dict__")
 QDEF0(MP_QSTR___eq__, 113, 6, "__eq__")
 QDEF0(MP_QSTR___eq__, 113, 6, "__eq__")
 QDEF1(MP_QSTR___file__, 3, 8, "__file__")
 QDEF1(MP_QSTR___file__, 3, 8, "__file__")
@@ -373,6 +390,7 @@ QDEF1(MP_QSTR_canvas_text_width, 86, 17, "canvas_text_width")
 QDEF1(MP_QSTR_canvas_update, 131, 13, "canvas_update")
 QDEF1(MP_QSTR_canvas_update, 131, 13, "canvas_update")
 QDEF1(MP_QSTR_canvas_width, 180, 12, "canvas_width")
 QDEF1(MP_QSTR_canvas_width, 180, 12, "canvas_width")
 QDEF1(MP_QSTR_closure, 116, 7, "closure")
 QDEF1(MP_QSTR_closure, 116, 7, "closure")
+QDEF1(MP_QSTR_debug, 212, 5, "debug")
 QDEF1(MP_QSTR_decode, 169, 6, "decode")
 QDEF1(MP_QSTR_decode, 169, 6, "decode")
 QDEF1(MP_QSTR_default, 206, 7, "default")
 QDEF1(MP_QSTR_default, 206, 7, "default")
 QDEF1(MP_QSTR_delattr, 219, 7, "delattr")
 QDEF1(MP_QSTR_delattr, 219, 7, "delattr")
@@ -387,34 +405,43 @@ QDEF1(MP_QSTR_difference_update, 156, 17, "difference_update")
 QDEF1(MP_QSTR_discard, 15, 7, "discard")
 QDEF1(MP_QSTR_discard, 15, 7, "discard")
 QDEF1(MP_QSTR_encode, 67, 6, "encode")
 QDEF1(MP_QSTR_encode, 67, 6, "encode")
 QDEF1(MP_QSTR_errno, 193, 5, "errno")
 QDEF1(MP_QSTR_errno, 193, 5, "errno")
+QDEF1(MP_QSTR_error, 189, 5, "error")
 QDEF1(MP_QSTR_filter, 37, 6, "filter")
 QDEF1(MP_QSTR_filter, 37, 6, "filter")
 QDEF1(MP_QSTR_flipperzero, 179, 11, "flipperzero")
 QDEF1(MP_QSTR_flipperzero, 179, 11, "flipperzero")
 QDEF1(MP_QSTR_float, 53, 5, "float")
 QDEF1(MP_QSTR_float, 53, 5, "float")
+QDEF1(MP_QSTR_flush, 97, 5, "flush")
 QDEF1(MP_QSTR_function, 39, 8, "function")
 QDEF1(MP_QSTR_function, 39, 8, "function")
 QDEF1(MP_QSTR_generator, 150, 9, "generator")
 QDEF1(MP_QSTR_generator, 150, 9, "generator")
+QDEF1(MP_QSTR_getEffectiveLevel, 40, 17, "getEffectiveLevel")
 QDEF1(MP_QSTR_getrandbits, 102, 11, "getrandbits")
 QDEF1(MP_QSTR_getrandbits, 102, 11, "getrandbits")
 QDEF1(MP_QSTR_gpio_deinit_pin, 120, 15, "gpio_deinit_pin")
 QDEF1(MP_QSTR_gpio_deinit_pin, 120, 15, "gpio_deinit_pin")
 QDEF1(MP_QSTR_gpio_get_pin, 85, 12, "gpio_get_pin")
 QDEF1(MP_QSTR_gpio_get_pin, 85, 12, "gpio_get_pin")
 QDEF1(MP_QSTR_gpio_init_pin, 185, 13, "gpio_init_pin")
 QDEF1(MP_QSTR_gpio_init_pin, 185, 13, "gpio_init_pin")
 QDEF1(MP_QSTR_gpio_set_pin, 65, 12, "gpio_set_pin")
 QDEF1(MP_QSTR_gpio_set_pin, 65, 12, "gpio_set_pin")
 QDEF1(MP_QSTR_hex, 112, 3, "hex")
 QDEF1(MP_QSTR_hex, 112, 3, "hex")
+QDEF1(MP_QSTR_info, 235, 4, "info")
 QDEF1(MP_QSTR_infrared_is_busy, 195, 16, "infrared_is_busy")
 QDEF1(MP_QSTR_infrared_is_busy, 195, 16, "infrared_is_busy")
 QDEF1(MP_QSTR_infrared_receive, 112, 16, "infrared_receive")
 QDEF1(MP_QSTR_infrared_receive, 112, 16, "infrared_receive")
 QDEF1(MP_QSTR_infrared_transmit, 81, 17, "infrared_transmit")
 QDEF1(MP_QSTR_infrared_transmit, 81, 17, "infrared_transmit")
 QDEF1(MP_QSTR_intersection, 40, 12, "intersection")
 QDEF1(MP_QSTR_intersection, 40, 12, "intersection")
 QDEF1(MP_QSTR_intersection_update, 6, 19, "intersection_update")
 QDEF1(MP_QSTR_intersection_update, 6, 19, "intersection_update")
+QDEF1(MP_QSTR_io, 35, 2, "io")
 QDEF1(MP_QSTR_isdisjoint, 247, 10, "isdisjoint")
 QDEF1(MP_QSTR_isdisjoint, 247, 10, "isdisjoint")
 QDEF1(MP_QSTR_issubset, 185, 8, "issubset")
 QDEF1(MP_QSTR_issubset, 185, 8, "issubset")
 QDEF1(MP_QSTR_issuperset, 252, 10, "issuperset")
 QDEF1(MP_QSTR_issuperset, 252, 10, "issuperset")
 QDEF1(MP_QSTR_iterator, 71, 8, "iterator")
 QDEF1(MP_QSTR_iterator, 71, 8, "iterator")
+QDEF1(MP_QSTR_level, 211, 5, "level")
 QDEF1(MP_QSTR_light_blink_set_color, 217, 21, "light_blink_set_color")
 QDEF1(MP_QSTR_light_blink_set_color, 217, 21, "light_blink_set_color")
 QDEF1(MP_QSTR_light_blink_start, 121, 17, "light_blink_start")
 QDEF1(MP_QSTR_light_blink_start, 121, 17, "light_blink_start")
 QDEF1(MP_QSTR_light_blink_stop, 33, 16, "light_blink_stop")
 QDEF1(MP_QSTR_light_blink_stop, 33, 16, "light_blink_stop")
 QDEF1(MP_QSTR_light_set, 134, 9, "light_set")
 QDEF1(MP_QSTR_light_set, 134, 9, "light_set")
+QDEF1(MP_QSTR_log, 33, 3, "log")
+QDEF1(MP_QSTR_logging, 70, 7, "logging")
 QDEF1(MP_QSTR_max, 177, 3, "max")
 QDEF1(MP_QSTR_max, 177, 3, "max")
 QDEF1(MP_QSTR_maximum_space_recursion_space_depth_space_exceeded, 115, 32, "maximum recursion depth exceeded")
 QDEF1(MP_QSTR_maximum_space_recursion_space_depth_space_exceeded, 115, 32, "maximum recursion depth exceeded")
 QDEF1(MP_QSTR_min, 175, 3, "min")
 QDEF1(MP_QSTR_min, 175, 3, "min")
 QDEF1(MP_QSTR_module, 191, 6, "module")
 QDEF1(MP_QSTR_module, 191, 6, "module")
+QDEF1(MP_QSTR_name, 162, 4, "name")
 QDEF1(MP_QSTR_oct, 253, 3, "oct")
 QDEF1(MP_QSTR_oct, 253, 3, "oct")
 QDEF1(MP_QSTR_on_gpio, 106, 7, "on_gpio")
 QDEF1(MP_QSTR_on_gpio, 106, 7, "on_gpio")
 QDEF1(MP_QSTR_on_input, 141, 8, "on_input")
 QDEF1(MP_QSTR_on_input, 141, 8, "on_input")
@@ -423,8 +450,12 @@ QDEF1(MP_QSTR_pwm_start, 240, 9, "pwm_start")
 QDEF1(MP_QSTR_pwm_stop, 200, 8, "pwm_stop")
 QDEF1(MP_QSTR_pwm_stop, 200, 8, "pwm_stop")
 QDEF1(MP_QSTR_random, 190, 6, "random")
 QDEF1(MP_QSTR_random, 190, 6, "random")
 QDEF1(MP_QSTR_rb, 213, 2, "rb")
 QDEF1(MP_QSTR_rb, 213, 2, "rb")
+QDEF1(MP_QSTR_readable, 93, 8, "readable")
+QDEF1(MP_QSTR_readlines, 106, 9, "readlines")
 QDEF1(MP_QSTR_reversed, 161, 8, "reversed")
 QDEF1(MP_QSTR_reversed, 161, 8, "reversed")
 QDEF1(MP_QSTR_seed, 146, 4, "seed")
 QDEF1(MP_QSTR_seed, 146, 4, "seed")
+QDEF1(MP_QSTR_seek, 157, 4, "seek")
+QDEF1(MP_QSTR_setLevel, 81, 8, "setLevel")
 QDEF1(MP_QSTR_sleep, 234, 5, "sleep")
 QDEF1(MP_QSTR_sleep, 234, 5, "sleep")
 QDEF1(MP_QSTR_sleep_ms, 11, 8, "sleep_ms")
 QDEF1(MP_QSTR_sleep_ms, 11, 8, "sleep_ms")
 QDEF1(MP_QSTR_sleep_us, 19, 8, "sleep_us")
 QDEF1(MP_QSTR_sleep_us, 19, 8, "sleep_us")
@@ -433,6 +464,7 @@ QDEF1(MP_QSTR_speaker_start, 1, 13, "speaker_start")
 QDEF1(MP_QSTR_speaker_stop, 153, 12, "speaker_stop")
 QDEF1(MP_QSTR_speaker_stop, 153, 12, "speaker_stop")
 QDEF1(MP_QSTR_symmetric_difference, 206, 20, "symmetric_difference")
 QDEF1(MP_QSTR_symmetric_difference, 206, 20, "symmetric_difference")
 QDEF1(MP_QSTR_symmetric_difference_update, 96, 27, "symmetric_difference_update")
 QDEF1(MP_QSTR_symmetric_difference_update, 96, 27, "symmetric_difference_update")
+QDEF1(MP_QSTR_tell, 20, 4, "tell")
 QDEF1(MP_QSTR_ticks_add, 157, 9, "ticks_add")
 QDEF1(MP_QSTR_ticks_add, 157, 9, "ticks_add")
 QDEF1(MP_QSTR_ticks_cpu, 26, 9, "ticks_cpu")
 QDEF1(MP_QSTR_ticks_cpu, 26, 9, "ticks_cpu")
 QDEF1(MP_QSTR_ticks_diff, 177, 10, "ticks_diff")
 QDEF1(MP_QSTR_ticks_diff, 177, 10, "ticks_diff")
@@ -440,8 +472,10 @@ QDEF1(MP_QSTR_ticks_ms, 66, 8, "ticks_ms")
 QDEF1(MP_QSTR_ticks_us, 90, 8, "ticks_us")
 QDEF1(MP_QSTR_ticks_us, 90, 8, "ticks_us")
 QDEF1(MP_QSTR_time, 240, 4, "time")
 QDEF1(MP_QSTR_time, 240, 4, "time")
 QDEF1(MP_QSTR_time_ns, 114, 7, "time_ns")
 QDEF1(MP_QSTR_time_ns, 114, 7, "time_ns")
+QDEF1(MP_QSTR_trace, 164, 5, "trace")
+QDEF1(MP_QSTR_uart_open, 220, 9, "uart_open")
 QDEF1(MP_QSTR_union, 246, 5, "union")
 QDEF1(MP_QSTR_union, 246, 5, "union")
 QDEF1(MP_QSTR_vibro_set, 216, 9, "vibro_set")
 QDEF1(MP_QSTR_vibro_set, 216, 9, "vibro_set")
+QDEF1(MP_QSTR_warn, 175, 4, "warn")
+QDEF1(MP_QSTR_writable, 247, 8, "writable")
 QDEF1(MP_QSTR__brace_open__colon__hash_b_brace_close_, 88, 5, "{:#b}")
 QDEF1(MP_QSTR__brace_open__colon__hash_b_brace_close_, 88, 5, "{:#b}")
-QDEF1(MP_QSTR__brace_open__colon__hash_o_brace_close_, 245, 5, "{:#o}")
-QDEF1(MP_QSTR__brace_open__colon__hash_x_brace_close_, 2, 5, "{:#x}")

+ 1 - 42
lib/micropython/mp_flipper_compiler.c

@@ -1,5 +1,6 @@
 #include <string.h>
 #include <string.h>
 
 
+#include "py/mperrno.h"
 #include "py/compile.h"
 #include "py/compile.h"
 #include "py/runtime.h"
 #include "py/runtime.h"
 #include "py/persistentcode.h"
 #include "py/persistentcode.h"
@@ -12,7 +13,6 @@
 #include "mp_flipper_halport.h"
 #include "mp_flipper_halport.h"
 
 
 void mp_flipper_exec_str(const char* code) {
 void mp_flipper_exec_str(const char* code) {
-#if MP_FLIPPER_IS_COMPILER
     nlr_buf_t nlr;
     nlr_buf_t nlr;
 
 
     if(nlr_push(&nlr) == 0) {
     if(nlr_push(&nlr) == 0) {
@@ -27,11 +27,9 @@ void mp_flipper_exec_str(const char* code) {
         // Uncaught exception: print it out.
         // Uncaught exception: print it out.
         mp_obj_print_exception(&mp_plat_print, (mp_obj_t)nlr.ret_val);
         mp_obj_print_exception(&mp_plat_print, (mp_obj_t)nlr.ret_val);
     }
     }
-#endif
 }
 }
 
 
 void mp_flipper_exec_py_file(const char* file_path) {
 void mp_flipper_exec_py_file(const char* file_path) {
-#if MP_FLIPPER_IS_COMPILER
     nlr_buf_t nlr;
     nlr_buf_t nlr;
 
 
     if(nlr_push(&nlr) == 0) {
     if(nlr_push(&nlr) == 0) {
@@ -56,43 +54,4 @@ void mp_flipper_exec_py_file(const char* file_path) {
         // Uncaught exception: print it out.
         // Uncaught exception: print it out.
         mp_obj_print_exception(&mp_plat_print, (mp_obj_t)nlr.ret_val);
         mp_obj_print_exception(&mp_plat_print, (mp_obj_t)nlr.ret_val);
     }
     }
-#endif
-}
-
-void mp_flipper_compile_and_save_file(const char* py_file_path, const char* mpy_file_path) {
-#if MP_FLIPPER_IS_COMPILER
-    nlr_buf_t nlr;
-
-    if(nlr_push(&nlr) == 0) {
-        mp_lexer_t* lex = mp_lexer_new_from_file(qstr_from_str(py_file_path));
-
-        mp_store_global(MP_QSTR___file__, MP_OBJ_NEW_QSTR(lex->source_name));
-
-        mp_parse_tree_t parse_tree = mp_parse(lex, MP_PARSE_FILE_INPUT);
-        mp_compiled_module_t cm;
-        cm.context = m_new_obj(mp_module_context_t);
-        mp_compile_to_raw_code(&parse_tree, lex->source_name, false, &cm);
-
-        mp_print_t* print = malloc(sizeof(mp_print_t));
-
-        print->data = mp_flipper_print_data_alloc();
-        print->print_strn = mp_flipper_print_strn;
-
-        mp_raw_code_save(&cm, print);
-
-        const char* data = mp_flipper_print_get_data(print->data);
-        size_t size = mp_flipper_print_get_data_length(print->data);
-
-        mp_flipper_save_file(mpy_file_path, data, size);
-
-        mp_flipper_print_data_free(print->data);
-
-        free(print);
-
-        nlr_pop();
-    } else {
-        // Uncaught exception: print it out.
-        mp_obj_print_exception(&mp_plat_print, (mp_obj_t)nlr.ret_val);
-    }
-#endif
 }
 }

+ 0 - 211
lib/micropython/mp_flipper_config.h

@@ -1,211 +0,0 @@
-// Need to provide a declaration/definition of alloca()
-#if defined(__FreeBSD__) || defined(__NetBSD__)
-#include <stdlib.h>
-#else
-#include <alloca.h>
-#endif
-
-#include <stdint.h>
-
-#ifndef MP_FLIPPER_IS_COMPILER
-#ifndef MP_FLIPPER_COMPILER
-#define MP_FLIPPER_IS_COMPILER (0)
-#else
-#define MP_FLIPPER_IS_COMPILER (1)
-#endif
-#endif
-
-#ifndef MP_FLIPPER_IS_RUNTIME
-#ifndef MP_FLIPPER_RUNTIME
-#define MP_FLIPPER_IS_RUNTIME (0)
-#else
-#define MP_FLIPPER_IS_RUNTIME (1)
-#endif
-#endif
-
-// Type definitions for the specific machine
-typedef int32_t mp_int_t; // must be pointer size
-typedef uint32_t mp_uint_t; // must be pointer size
-typedef long mp_off_t;
-
-#ifdef MP_FLIPPER_SPLIT_HEAP
-#define MICROPY_GC_SPLIT_HEAP (1)
-#define MICROPY_GC_SPLIT_HEAP_AUTO (1)
-#endif
-
-#define MICROPY_MPHALPORT_H "mp_flipper_halport.h"
-
-#define MICROPY_MIN_USE_CORTEX_CPU (1)
-#define MICROPY_MIN_USE_STM32_MCU (1)
-
-#define MICROPY_HW_BOARD_NAME "Flipper Zero"
-#define MICROPY_HW_MCU_NAME "STM32WB55RG"
-
-#define MICROPY_CONFIG_ROM_LEVEL (MICROPY_CONFIG_ROM_LEVEL_CORE_FEATURES)
-
-#ifdef MP_FLIPPER_MPY_SUPPORT
-#define MICROPY_PERSISTENT_CODE_LOAD (MP_FLIPPER_IS_RUNTIME)
-#define MICROPY_PERSISTENT_CODE_SAVE (MP_FLIPPER_IS_COMPILER)
-#else
-#define MICROPY_PERSISTENT_CODE_LOAD (0)
-#define MICROPY_PERSISTENT_CODE_SAVE (0)
-#endif
-#define MICROPY_PERSISTENT_CODE_SAVE_FILE (0)
-
-#define MICROPY_ENABLE_COMPILER (MP_FLIPPER_IS_COMPILER)
-#define MICROPY_ENABLE_GC (1)
-#define MICROPY_PY_GC_COLLECT_RETVAL (1)
-#define MICROPY_ENABLE_PYSTACK (0)
-#define MICROPY_STACK_CHECK (1)
-#define MICROPY_ALLOC_PATH_MAX (256)
-
-#define MICROPY_ENABLE_FINALISER (0)
-
-#ifdef MP_FLIPPER_FIRMWARE
-#define MICROPY_ERROR_REPORTING (MICROPY_ERROR_REPORTING_TERSE)
-#else
-#define MICROPY_ERROR_REPORTING (MICROPY_ERROR_REPORTING_NONE)
-#endif
-
-#define MICROPY_GC_STACK_ENTRY_TYPE uint32_t
-
-#define MICROPY_PY___FILE__ (1)
-#define MICROPY_ENABLE_EXTERNAL_IMPORT (1)
-#define MICROPY_READER_VFS (1)
-
-#define MICROPY_ENABLE_VM_ABORT (0)
-
-#define MICROPY_PY_ERRNO (0)
-#define MICROPY_USE_INTERNAL_ERRNO (0)
-#define MICROPY_PY_ERRNO_ERRORCODE (0)
-
-#define MICROPY_PY_TIME (1)
-#define MICROPY_PY_TIME_TIME_TIME_NS (1)
-
-#define MICROPY_PY_RANDOM (1)
-#define MICROPY_PY_RANDOM_EXTRA_FUNCS (0)
-
-#define MICROPY_PY_RANDOM_SEED_INIT_FUNC (mp_flipper_seed_init())
-
-#ifdef MP_FLIPPER_FIRMWARE
-#define MICROPY_LONGINT_IMPL (MICROPY_LONGINT_IMPL_LONGLONG)
-#else
-#define MICROPY_LONGINT_IMPL (MICROPY_LONGINT_IMPL_NONE)
-#endif
-
-#ifdef MP_FLIPPER_FIRMWARE
-#define MICROPY_FLOAT_IMPL (MICROPY_FLOAT_IMPL_DOUBLE)
-#else
-#define MICROPY_FLOAT_IMPL (MICROPY_FLOAT_IMPL_FLOAT)
-#endif
-
-#ifdef MP_FLIPPER_FIRMWARE
-#define MICROPY_HELPER_REPL (MP_FLIPPER_IS_COMPILER)
-#else
-#define MICROPY_HELPER_REPL (0)
-#endif
-
-#define MICROPY_ENABLE_SOURCE_LINE (0)
-#define MICROPY_ENABLE_DOC_STRING (0)
-
-#define MICROPY_REPL_INFO (0)
-#define MICROPY_REPL_EMACS_KEYS (0)
-#define MICROPY_REPL_EMACS_WORDS_MOVE (0)
-#define MICROPY_REPL_EMACS_EXTRA_WORDS_MOVE (0)
-#define MICROPY_REPL_AUTO_INDENT (0)
-#define MICROPY_REPL_EVENT_DRIVEN (0)
-#define MICROPY_READLINE_HISTORY_SIZE (0)
-
-#define MICROPY_CPYTHON_COMPAT (1)
-#define MICROPY_FULL_CHECKS (0)
-#define MICROPY_BUILTIN_METHOD_CHECK_SELF_ARG (0)
-
-#define MICROPY_MODULE_FROZEN_MPY (0)
-
-#define MICROPY_PY_CMATH (0)
-#define MICROPY_PY_BUILTINS_COMPLEX (0)
-#define MICROPY_MULTIPLE_INHERITANCE (0)
-#define MICROPY_MODULE_GETATTR (0)
-#define MICROPY_PY_FUNCTION_ATTRS (1)
-#define MICROPY_PY_DESCRIPTORS (0)
-#define MICROPY_PY_ASYNC_AWAIT (0)
-#define MICROPY_PY_ASSIGN_EXPR (0)
-#define MICROPY_PY_GENERATOR_PEND_THROW (0)
-#define MICROPY_PY_BUILTINS_BYTES_HEX (0)
-#define MICROPY_PY_BUILTINS_STR_UNICODE (0)
-#define MICROPY_PY_BUILTINS_STR_CENTER (0)
-#define MICROPY_PY_BUILTINS_STR_COUNT (0)
-#define MICROPY_PY_BUILTINS_STR_OP_MODULO (0)
-#define MICROPY_PY_BUILTINS_STR_PARTITION (0)
-#define MICROPY_PY_BUILTINS_STR_SPLITLINES (0)
-#define MICROPY_PY_BUILTINS_BYTEARRAY (0)
-#define MICROPY_PY_BUILTINS_DICT_FROMKEYS (0)
-#define MICROPY_PY_BUILTINS_MEMORYVIEW (0)
-#define MICROPY_PY_BUILTINS_SET (1)
-#define MICROPY_PY_BUILTINS_SLICE (0)
-#define MICROPY_PY_BUILTINS_SLICE_ATTRS (0)
-#define MICROPY_PY_BUILTINS_SLICE_INDICES (0)
-#define MICROPY_PY_BUILTINS_FROZENSET (0)
-#define MICROPY_PY_BUILTINS_PROPERTY (0)
-#define MICROPY_PY_BUILTINS_RANGE_ATTRS (0)
-#define MICROPY_PY_BUILTINS_RANGE_BINOP (0)
-#define MICROPY_PY_BUILTINS_NEXT2 (0)
-#define MICROPY_PY_BUILTINS_ROUND_INT (0)
-#define MICROPY_PY_ALL_SPECIAL_METHODS (0)
-#define MICROPY_PY_REVERSE_SPECIAL_METHODS (0)
-#define MICROPY_PY_BUILTINS_ENUMERATE (0)
-#define MICROPY_PY_BUILTINS_COMPILE (0)
-#define MICROPY_PY_BUILTINS_EVAL_EXEC (0)
-#define MICROPY_PY_BUILTINS_EXECFILE (0)
-#define MICROPY_PY_BUILTINS_FILTER (1)
-#define MICROPY_PY_BUILTINS_REVERSED (1)
-#define MICROPY_PY_BUILTINS_NOTIMPLEMENTED (0)
-#define MICROPY_PY_BUILTINS_INPUT (0)
-#define MICROPY_PY_BUILTINS_MIN_MAX (1)
-#define MICROPY_PY_BUILTINS_POW3 (0)
-#define MICROPY_PY_BUILTINS_HELP (0)
-#define MICROPY_PY_MICROPYTHON (0)
-#define MICROPY_PY_MICROPYTHON_MEM_INFO (0)
-#define MICROPY_PY_MICROPYTHON_STACK_USE (0)
-#define MICROPY_PY_MICROPYTHON_HEAP_LOCKED (0)
-#define MICROPY_PY_ARRAY (0)
-#define MICROPY_PY_ARRAY_SLICE_ASSIGN (0)
-#define MICROPY_PY_ATTRTUPLE (0)
-#define MICROPY_PY_COLLECTIONS (0)
-#define MICROPY_PY_STRUCT (0)
-#define MICROPY_PY_GC (0)
-#define MICROPY_PY_SYS (0)
-#define MICROPY_PY_SYS_MODULES (0)
-#define MICROPY_PY_SELECT_SELECT (0)
-#define MICROPY_PY_SYS_EXIT (0)
-#define MICROPY_PY_RE (0)
-#define MICROPY_PY_CRYPTOLIB (0)
-#define MICROPY_PY_VFS (0)
-#define MICROPY_ENABLE_SCHEDULER (1)
-#define MICROPY_MODULE_BUILTIN_INIT (1)
-
-#ifdef MP_FLIPPER_MATH
-#define MICROPY_PY_MATH (1)
-#else
-#define MICROPY_PY_MATH (0)
-#endif
-
-#ifdef MP_FLIPPER_IO
-#define MICROPY_PY_IO (1)
-#else
-#define MICROPY_PY_IO (0)
-#endif
-
-#ifdef MP_FLIPPER_JSON
-#define MICROPY_PY_JSON (1)
-#define MICROPY_PY_JSON_SEPARATORS (1)
-#else
-#define MICROPY_PY_JSON (0)
-#define MICROPY_PY_JSON_SEPARATORS (0)
-#endif
-
-#define MICROPY_COMP_CONST_FOLDING (0)
-#define MICROPY_COMP_CONST_TUPLE (0)
-#define MICROPY_COMP_CONST_LITERAL (0)
-#define MICROPY_COMP_CONST (0)
-#define MICROPY_COMP_DOUBLE_TUPLE_ASSIGN (0)

+ 0 - 7
lib/micropython/mp_flipper_config_fap.h

@@ -1,7 +0,0 @@
-#pragma once
-
-#define MP_FLIPPER_COMPILER
-#define MP_FLIPPER_RUNTIME
-#define MP_FLIPPER_SPLIT_HEAP
-
-#include "mp_flipper_config.h"

+ 0 - 8
lib/micropython/mp_flipper_config_firmware.h

@@ -1,8 +0,0 @@
-#pragma once
-
-#define MP_FLIPPER_COMPILER
-#define MP_FLIPPER_RUNTIME
-#define MP_FLIPPER_SPLIT_HEAP
-#define MP_FLIPPER_IO
-
-#include "mp_flipper_config.h"

+ 1 - 0
lib/micropython/mp_flipper_file_reader.c

@@ -1,4 +1,5 @@
 #include "py/reader.h"
 #include "py/reader.h"
+#include "py/qstr.h"
 
 
 #include "mp_flipper_file_reader.h"
 #include "mp_flipper_file_reader.h"
 
 

+ 284 - 0
lib/micropython/mp_flipper_fileio.c

@@ -0,0 +1,284 @@
+#include <stdio.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <string.h>
+
+#include "py/obj.h"
+#include "py/stream.h"
+#include "py/runtime.h"
+#include "py/mperrno.h"
+
+#include "mp_flipper_fileio.h"
+
+extern const mp_obj_type_t mp_flipper_binary_fileio_type;
+extern const mp_obj_type_t mp_flipper_text_fileio_type;
+
+typedef struct _mp_flipper_fileio_file_descriptor_t {
+    mp_obj_base_t base;
+    void* handle;
+    mp_obj_t name;
+    uint8_t access_mode;
+    uint8_t open_mode;
+} mp_flipper_fileio_file_descriptor_t;
+
+void* mp_flipper_file_new_file_descriptor(void* handle, const char* name, uint8_t access_mode, uint8_t open_mode, bool is_text) {
+    mp_flipper_fileio_file_descriptor_t* fd = mp_obj_malloc_with_finaliser(
+        mp_flipper_fileio_file_descriptor_t, is_text ? &mp_flipper_text_fileio_type : &mp_flipper_binary_fileio_type);
+
+    fd->handle = handle;
+    fd->name = mp_obj_new_str(name, strlen(name));
+    fd->access_mode = access_mode;
+    fd->open_mode = open_mode;
+
+    return fd;
+}
+
+static mp_uint_t mp_flipper_fileio_read(mp_obj_t self, void* buf, mp_uint_t size, int* errcode) {
+    mp_flipper_fileio_file_descriptor_t* fd = MP_OBJ_TO_PTR(self);
+
+    if(fd->handle == NULL) {
+        *errcode = MP_EIO;
+
+        return MP_STREAM_ERROR;
+    }
+
+    return mp_flipper_file_read(fd->handle, buf, size, errcode);
+}
+
+static mp_uint_t mp_flipper_fileio_write(mp_obj_t self, const void* buf, mp_uint_t size, int* errcode) {
+    mp_flipper_fileio_file_descriptor_t* fd = MP_OBJ_TO_PTR(self);
+
+    if(fd->handle == NULL) {
+        *errcode = MP_EIO;
+
+        return MP_STREAM_ERROR;
+    }
+
+    return mp_flipper_file_write(fd->handle, buf, size, errcode);
+}
+
+static mp_uint_t mp_flipper_fileio_ioctl(mp_obj_t self, mp_uint_t request, uintptr_t arg, int* errcode) {
+    mp_flipper_fileio_file_descriptor_t* fd = MP_OBJ_TO_PTR(self);
+
+    if(fd->handle == NULL) {
+        return 0;
+    }
+
+    if(request == MP_STREAM_SEEK) {
+        struct mp_stream_seek_t* seek = (struct mp_stream_seek_t*)(uintptr_t)arg;
+        size_t position;
+        bool success;
+
+        switch(seek->whence) {
+        case MP_SEEK_SET:
+            mp_flipper_file_seek(fd->handle, seek->offset);
+
+            break;
+
+        case MP_SEEK_CUR:
+            position = mp_flipper_file_tell(fd->handle);
+
+            mp_flipper_file_seek(fd->handle, position + seek->offset);
+
+            break;
+
+        case MP_SEEK_END:
+            position = mp_flipper_file_size(fd->handle);
+
+            mp_flipper_file_seek(fd->handle, position + seek->offset);
+
+            break;
+        }
+
+        seek->offset = mp_flipper_file_tell(fd->handle);
+
+        return 0;
+    }
+
+    if(request == MP_STREAM_FLUSH) {
+        if(!mp_flipper_file_sync(fd->handle)) {
+            *errcode = MP_EIO;
+
+            return MP_STREAM_ERROR;
+        }
+
+        return 0;
+    }
+
+    if(request == MP_STREAM_CLOSE) {
+        if(!mp_flipper_file_close(fd->handle)) {
+            *errcode = MP_EIO;
+
+            fd->handle = NULL;
+
+            return MP_STREAM_ERROR;
+        }
+
+        fd->handle = NULL;
+
+        return 0;
+    }
+
+    *errcode = MP_EINVAL;
+
+    return MP_STREAM_ERROR;
+}
+
+static void fileio_attr(mp_obj_t self_in, qstr attr, mp_obj_t* dest) {
+    mp_flipper_fileio_file_descriptor_t* fd = MP_OBJ_TO_PTR(self_in);
+
+    if(dest[0] == MP_OBJ_NULL) {
+        if(attr == MP_QSTR_name) {
+            dest[0] = fd->name;
+
+            return;
+        }
+
+        if(attr == MP_QSTR_readable) {
+            dest[0] = (fd->access_mode & MP_FLIPPER_FILE_ACCESS_MODE_READ) ? mp_const_true : mp_const_false;
+
+            return;
+        }
+
+        if(attr == MP_QSTR_writable) {
+            dest[0] = (fd->access_mode & MP_FLIPPER_FILE_ACCESS_MODE_WRITE) ? mp_const_true : mp_const_false;
+
+            return;
+        }
+
+        dest[1] = MP_OBJ_SENTINEL;
+    } else {
+        return;
+    }
+}
+
+static const mp_map_elem_t mp_flipper_file_locals_dict_table[] = {
+    {MP_ROM_QSTR(MP_QSTR_read), MP_ROM_PTR(&mp_stream_read_obj)},
+    {MP_ROM_QSTR(MP_QSTR_readline), MP_ROM_PTR(&mp_stream_unbuffered_readline_obj)},
+    {MP_ROM_QSTR(MP_QSTR_readlines), MP_ROM_PTR(&mp_stream_unbuffered_readlines_obj)},
+    {MP_ROM_QSTR(MP_QSTR_write), MP_ROM_PTR(&mp_stream_write_obj)},
+    {MP_ROM_QSTR(MP_QSTR_flush), MP_ROM_PTR(&mp_stream_flush_obj)},
+    {MP_ROM_QSTR(MP_QSTR_close), MP_ROM_PTR(&mp_stream_close_obj)},
+    {MP_ROM_QSTR(MP_QSTR_seek), MP_ROM_PTR(&mp_stream_seek_obj)},
+    {MP_ROM_QSTR(MP_QSTR_tell), MP_ROM_PTR(&mp_stream_tell_obj)},
+    {MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&mp_stream_close_obj)},
+    {MP_ROM_QSTR(MP_QSTR___enter__), MP_ROM_PTR(&mp_identity_obj)},
+    {MP_ROM_QSTR(MP_QSTR___exit__), MP_ROM_PTR(&mp_stream___exit___obj)},
+};
+static MP_DEFINE_CONST_DICT(mp_flipper_file_locals_dict, mp_flipper_file_locals_dict_table);
+
+static const mp_stream_p_t mp_flipper_binary_fileio_stream_p = {
+    .read = mp_flipper_fileio_read,
+    .write = mp_flipper_fileio_write,
+    .ioctl = mp_flipper_fileio_ioctl,
+    .is_text = false,
+};
+
+MP_DEFINE_CONST_OBJ_TYPE(
+    mp_flipper_binary_fileio_type,
+    MP_QSTR_BinaryFileIO,
+    MP_TYPE_FLAG_ITER_IS_STREAM,
+    protocol,
+    &mp_flipper_binary_fileio_stream_p,
+    attr,
+    fileio_attr,
+    locals_dict,
+    &mp_flipper_file_locals_dict);
+
+static const mp_stream_p_t mp_flipper_text_fileio_stream_p = {
+    .read = mp_flipper_fileio_read,
+    .write = mp_flipper_fileio_write,
+    .ioctl = mp_flipper_fileio_ioctl,
+    .is_text = true,
+};
+
+MP_DEFINE_CONST_OBJ_TYPE(
+    mp_flipper_text_fileio_type,
+    MP_QSTR_TextFileIO,
+    MP_TYPE_FLAG_ITER_IS_STREAM,
+    protocol,
+    &mp_flipper_text_fileio_stream_p,
+    attr,
+    fileio_attr,
+    locals_dict,
+    &mp_flipper_file_locals_dict);
+
+mp_obj_t mp_flipper_builtin_open(size_t n_args, const mp_obj_t* args, mp_map_t* kwargs) {
+    const char* file_name = mp_obj_str_get_str(args[0]);
+
+    uint8_t access_mode = MP_FLIPPER_FILE_ACCESS_MODE_READ;
+    uint8_t open_mode = MP_FLIPPER_FILE_OPEN_MODE_OPEN_EXIST;
+    bool is_text = true;
+
+    if(n_args > 1) {
+        size_t len;
+
+        const char* mode = mp_obj_str_get_data(args[1], &len);
+
+        for(size_t i = 0; i < len; i++) {
+            if(i == 0 && mode[i] == 'r') {
+                access_mode = MP_FLIPPER_FILE_ACCESS_MODE_READ;
+                open_mode = MP_FLIPPER_FILE_OPEN_MODE_OPEN_EXIST;
+
+                continue;
+            }
+
+            if(i == 0 && mode[i] == 'w') {
+                access_mode = MP_FLIPPER_FILE_ACCESS_MODE_WRITE;
+                open_mode = MP_FLIPPER_FILE_OPEN_MODE_CREATE_ALWAYS;
+
+                continue;
+            }
+
+            if(i == 1 && mode[i] == 'b') {
+                is_text = false;
+
+                continue;
+            }
+
+            if(i == 1 && mode[i] == 't') {
+                is_text = true;
+
+                continue;
+            }
+
+            if(i >= 1 && mode[i] == '+') {
+                access_mode = MP_FLIPPER_FILE_ACCESS_MODE_READ | MP_FLIPPER_FILE_ACCESS_MODE_WRITE;
+                open_mode = MP_FLIPPER_FILE_OPEN_MODE_OPEN_APPEND;
+
+                continue;
+            }
+
+            mp_raise_OSError(MP_EINVAL);
+        }
+    }
+
+    void* handle = mp_flipper_file_open(file_name, access_mode, open_mode);
+    void* fd = mp_flipper_file_new_file_descriptor(handle, file_name, access_mode, open_mode, is_text);
+
+    if(handle == NULL) {
+        mp_raise_OSError(MP_ENOENT);
+    }
+
+    return MP_OBJ_FROM_PTR(fd);
+}
+MP_DEFINE_CONST_FUN_OBJ_KW(mp_flipper_builtin_open_obj, 1, mp_flipper_builtin_open);
+
+static const mp_rom_map_elem_t mp_module_io_globals_table[] = {
+    {MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_io)},
+    {MP_ROM_QSTR(MP_QSTR_open), MP_ROM_PTR(&mp_flipper_builtin_open_obj)},
+    {MP_ROM_QSTR(MP_QSTR_BinaryFileIO), MP_ROM_PTR(&mp_flipper_binary_fileio_type)},
+    {MP_ROM_QSTR(MP_QSTR_TextFileIO), MP_ROM_PTR(&mp_flipper_text_fileio_type)},
+    {MP_ROM_QSTR(MP_QSTR_SEEK_SET), MP_ROM_INT(MP_SEEK_SET)},
+    {MP_ROM_QSTR(MP_QSTR_SEEK_CUR), MP_ROM_INT(MP_SEEK_CUR)},
+    {MP_ROM_QSTR(MP_QSTR_SEEK_END), MP_ROM_INT(MP_SEEK_END)},
+};
+
+static MP_DEFINE_CONST_DICT(mp_module_io_globals, mp_module_io_globals_table);
+
+const mp_obj_module_t mp_module_io = {
+    .base = {&mp_type_module},
+    .globals = (mp_obj_dict_t*)&mp_module_io_globals,
+};
+
+MP_REGISTER_EXTENSIBLE_MODULE(MP_QSTR_io, mp_module_io);

+ 25 - 0
lib/micropython/mp_flipper_fileio.h

@@ -0,0 +1,25 @@
+#pragma once
+
+#include <stdio.h>
+#include <stdint.h>
+#include <stdbool.h>
+
+extern uint8_t MP_FLIPPER_FILE_ACCESS_MODE_READ;
+extern uint8_t MP_FLIPPER_FILE_ACCESS_MODE_WRITE;
+
+extern uint8_t MP_FLIPPER_FILE_OPEN_MODE_OPEN_EXIST;
+extern uint8_t MP_FLIPPER_FILE_OPEN_MODE_OPEN_ALWAYS;
+extern uint8_t MP_FLIPPER_FILE_OPEN_MODE_OPEN_APPEND;
+extern uint8_t MP_FLIPPER_FILE_OPEN_MODE_CREATE_NEW;
+extern uint8_t MP_FLIPPER_FILE_OPEN_MODE_CREATE_ALWAYS;
+
+void* mp_flipper_file_open(const char* name, uint8_t access_mode, uint8_t open_mode);
+void* mp_flipper_file_new_file_descriptor(void* handle, const char* name, uint8_t access_mode, uint8_t open_mode, bool is_text);
+bool mp_flipper_file_close(void* handle);
+size_t mp_flipper_file_seek(void* handle, uint32_t offset);
+size_t mp_flipper_file_tell(void* handle);
+size_t mp_flipper_file_size(void* handle);
+bool mp_flipper_file_sync(void* handle);
+bool mp_flipper_file_eof(void* handle);
+size_t mp_flipper_file_read(void* handle, void* buffer, size_t size, int* errcode);
+size_t mp_flipper_file_write(void* handle, const void* buffer, size_t size, int* errcode);

+ 5 - 9
lib/micropython/mp_flipper_halport.c

@@ -1,15 +1,13 @@
+#include "py/mperrno.h"
+#include "py/obj.h"
+#include "py/runtime.h"
 #include <stdio.h>
 #include <stdio.h>
 
 
 #include "py/mphal.h"
 #include "py/mphal.h"
 #include "py/builtin.h"
 #include "py/builtin.h"
 
 
 #include "mp_flipper_halport.h"
 #include "mp_flipper_halport.h"
-
-mp_obj_t mp_builtin_open(size_t n_args, const mp_obj_t* args, mp_map_t* kwargs) {
-    return mp_const_none;
-}
-
-MP_DEFINE_CONST_FUN_OBJ_KW(mp_builtin_open_obj, 1, mp_builtin_open);
+#include "mp_flipper_fileio.h"
 
 
 void mp_hal_stdout_tx_str(const char* str) {
 void mp_hal_stdout_tx_str(const char* str) {
     mp_flipper_stdout_tx_str(str);
     mp_flipper_stdout_tx_str(str);
@@ -33,8 +31,6 @@ mp_import_stat_t mp_import_stat(const char* path) {
     return MP_IMPORT_STAT_NO_EXIST;
     return MP_IMPORT_STAT_NO_EXIST;
 }
 }
 
 
-#ifdef MP_FLIPPER_SPLIT_HEAP
 size_t gc_get_max_new_split(void) {
 size_t gc_get_max_new_split(void) {
     return mp_flipper_gc_get_max_new_split();
     return mp_flipper_gc_get_max_new_split();
-}
-#endif
+}

+ 0 - 2
lib/micropython/mp_flipper_halport.h

@@ -16,6 +16,4 @@ void mp_flipper_stdout_tx_strn_cooked(const char* str, size_t len);
 
 
 mp_flipper_import_stat_t mp_flipper_import_stat(const char* path);
 mp_flipper_import_stat_t mp_flipper_import_stat(const char* path);
 
 
-#ifdef MP_FLIPPER_SPLIT_HEAP
 size_t mp_flipper_gc_get_max_new_split();
 size_t mp_flipper_gc_get_max_new_split();
-#endif

+ 110 - 0
lib/micropython/mp_flipper_logging.c

@@ -0,0 +1,110 @@
+#include "py/objint.h"
+#include <stdio.h>
+#include <stdbool.h>
+#include <stdint.h>
+
+#include "py/obj.h"
+
+#include "mp_flipper_logging.h"
+
+static struct _mp_obj_int_t mp_flipper_log_level_obj = {&mp_type_int, MP_FLIPPER_LOG_LEVEL_INFO};
+
+static mp_obj_t mp_flipper_logging_log_internal(uint8_t level, size_t n_args, const mp_obj_t* args) {
+    if(n_args < 1 || level > mp_flipper_log_get_effective_level()) {
+        return mp_const_none;
+    }
+
+    mp_obj_t message = args[0];
+
+    if(n_args > 1) {
+        mp_obj_t values = mp_obj_new_tuple(n_args - 1, &args[1]);
+
+        message = mp_obj_str_binary_op(MP_BINARY_OP_MODULO, args[0], values);
+    }
+
+    mp_flipper_log(level, mp_obj_str_get_str(message));
+
+    return mp_const_none;
+}
+
+static mp_obj_t mp_flipper_logging_set_level(mp_obj_t raw_level) {
+    uint8_t level = mp_obj_get_int(raw_level);
+
+    if(level >= MP_FLIPPER_LOG_LEVEL_NONE && level <= MP_FLIPPER_LOG_LEVEL_TRACE) {
+        mp_flipper_log_level_obj.val = level;
+    }
+
+    return mp_const_none;
+}
+static MP_DEFINE_CONST_FUN_OBJ_1(mp_flipper_logging_set_level_obj, mp_flipper_logging_set_level);
+
+static mp_obj_t mp_flipper_logging_get_effective_level() {
+    uint8_t level = mp_flipper_log_get_effective_level();
+
+    return mp_obj_new_int_from_uint(level);
+}
+static MP_DEFINE_CONST_FUN_OBJ_0(mp_flipper_logging_get_effective_level_obj, mp_flipper_logging_get_effective_level);
+
+static mp_obj_t mp_flipper_logging_log(size_t n_args, const mp_obj_t* args) {
+    if(n_args < 2) {
+        return mp_const_none;
+    }
+
+    uint8_t level = mp_obj_get_int(args[0]);
+
+    return mp_flipper_logging_log_internal(level, n_args - 1, &args[1]);
+}
+static MP_DEFINE_CONST_FUN_OBJ_VAR(mp_flipper_logging_log_obj, 2, mp_flipper_logging_log);
+
+static mp_obj_t mp_flipper_logging_trace(size_t n_args, const mp_obj_t* args) {
+    return mp_flipper_logging_log_internal(MP_FLIPPER_LOG_LEVEL_TRACE, n_args, args);
+}
+static MP_DEFINE_CONST_FUN_OBJ_VAR(mp_flipper_logging_trace_obj, 1, mp_flipper_logging_trace);
+
+static mp_obj_t mp_flipper_logging_debug(size_t n_args, const mp_obj_t* args) {
+    return mp_flipper_logging_log_internal(MP_FLIPPER_LOG_LEVEL_DEBUG, n_args, args);
+}
+static MP_DEFINE_CONST_FUN_OBJ_VAR(mp_flipper_logging_debug_obj, 1, mp_flipper_logging_debug);
+
+static mp_obj_t mp_flipper_logging_info(size_t n_args, const mp_obj_t* args) {
+    return mp_flipper_logging_log_internal(MP_FLIPPER_LOG_LEVEL_INFO, n_args, args);
+}
+static MP_DEFINE_CONST_FUN_OBJ_VAR(mp_flipper_logging_info_obj, 1, mp_flipper_logging_info);
+
+static mp_obj_t mp_flipper_logging_warn(size_t n_args, const mp_obj_t* args) {
+    return mp_flipper_logging_log_internal(MP_FLIPPER_LOG_LEVEL_WARN, n_args, args);
+}
+static MP_DEFINE_CONST_FUN_OBJ_VAR(mp_flipper_logging_warn_obj, 1, mp_flipper_logging_warn);
+
+static mp_obj_t mp_flipper_logging_error(size_t n_args, const mp_obj_t* args) {
+    return mp_flipper_logging_log_internal(MP_FLIPPER_LOG_LEVEL_ERROR, n_args, args);
+}
+static MP_DEFINE_CONST_FUN_OBJ_VAR(mp_flipper_logging_error_obj, 1, mp_flipper_logging_error);
+
+static const mp_rom_map_elem_t mp_module_logging_globals_table[] = {
+    {MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_logging)},
+    {MP_ROM_QSTR(MP_QSTR_level), MP_ROM_PTR(&mp_flipper_log_level_obj)},
+    {MP_ROM_QSTR(MP_QSTR_setLevel), MP_ROM_PTR(&mp_flipper_logging_set_level_obj)},
+    {MP_ROM_QSTR(MP_QSTR_getEffectiveLevel), MP_ROM_PTR(&mp_flipper_logging_get_effective_level_obj)},
+    {MP_ROM_QSTR(MP_QSTR_trace), MP_ROM_PTR(&mp_flipper_logging_trace_obj)},
+    {MP_ROM_QSTR(MP_QSTR_debug), MP_ROM_PTR(&mp_flipper_logging_debug_obj)},
+    {MP_ROM_QSTR(MP_QSTR_info), MP_ROM_PTR(&mp_flipper_logging_info_obj)},
+    {MP_ROM_QSTR(MP_QSTR_warn), MP_ROM_PTR(&mp_flipper_logging_warn_obj)},
+    {MP_ROM_QSTR(MP_QSTR_error), MP_ROM_PTR(&mp_flipper_logging_error_obj)},
+    {MP_ROM_QSTR(MP_QSTR_log), MP_ROM_PTR(&mp_flipper_logging_log_obj)},
+    {MP_ROM_QSTR(MP_QSTR_TRACE), MP_ROM_INT(MP_FLIPPER_LOG_LEVEL_TRACE)},
+    {MP_ROM_QSTR(MP_QSTR_DEBUG), MP_ROM_INT(MP_FLIPPER_LOG_LEVEL_DEBUG)},
+    {MP_ROM_QSTR(MP_QSTR_INFO), MP_ROM_INT(MP_FLIPPER_LOG_LEVEL_INFO)},
+    {MP_ROM_QSTR(MP_QSTR_WARN), MP_ROM_INT(MP_FLIPPER_LOG_LEVEL_WARN)},
+    {MP_ROM_QSTR(MP_QSTR_ERROR), MP_ROM_INT(MP_FLIPPER_LOG_LEVEL_ERROR)},
+    {MP_ROM_QSTR(MP_QSTR_NONE), MP_ROM_INT(MP_FLIPPER_LOG_LEVEL_NONE)},
+};
+
+static MP_DEFINE_CONST_DICT(mp_module_logging_globals, mp_module_logging_globals_table);
+
+const mp_obj_module_t mp_module_logging = {
+    .base = {&mp_type_module},
+    .globals = (mp_obj_dict_t*)&mp_module_logging_globals,
+};
+
+MP_REGISTER_EXTENSIBLE_MODULE(MP_QSTR_logging, mp_module_logging);

+ 14 - 0
lib/micropython/mp_flipper_logging.h

@@ -0,0 +1,14 @@
+#pragma once
+
+#include <stdint.h>
+#include <stdbool.h>
+
+#define MP_FLIPPER_LOG_LEVEL_TRACE (6)
+#define MP_FLIPPER_LOG_LEVEL_DEBUG (5)
+#define MP_FLIPPER_LOG_LEVEL_INFO (4)
+#define MP_FLIPPER_LOG_LEVEL_WARN (3)
+#define MP_FLIPPER_LOG_LEVEL_ERROR (2)
+#define MP_FLIPPER_LOG_LEVEL_NONE (1)
+
+uint8_t mp_flipper_log_get_effective_level();
+void mp_flipper_log(uint8_t raw_level, const char* message);

+ 132 - 0
lib/micropython/mp_flipper_modflipperzero.c

@@ -1,9 +1,11 @@
 #include <stdint.h>
 #include <stdint.h>
 #include <stdio.h>
 #include <stdio.h>
 
 
+#include "py/mperrno.h"
 #include "py/objint.h"
 #include "py/objint.h"
 #include "py/objfun.h"
 #include "py/objfun.h"
 #include "py/obj.h"
 #include "py/obj.h"
+#include "py/stream.h"
 #include "py/runtime.h"
 #include "py/runtime.h"
 
 
 #include "mp_flipper_modflipperzero.h"
 #include "mp_flipper_modflipperzero.h"
@@ -617,6 +619,131 @@ static mp_obj_t flipperzero_infrared_is_busy() {
 }
 }
 static MP_DEFINE_CONST_FUN_OBJ_0(flipperzero_infrared_is_busy_obj, flipperzero_infrared_is_busy);
 static MP_DEFINE_CONST_FUN_OBJ_0(flipperzero_infrared_is_busy_obj, flipperzero_infrared_is_busy);
 
 
+extern const mp_obj_type_t flipperzero_uart_connection_type;
+
+typedef struct _flipperzero_uart_connection_t {
+    mp_obj_base_t base;
+    void* handle;
+    mp_obj_t mode;
+    mp_obj_t baud_rate;
+} flipperzero_uart_connection_t;
+
+static mp_obj_t flipperzero_uart_open(mp_obj_t raw_mode, mp_obj_t raw_baud_rate) {
+    uint8_t mode = mp_obj_get_int(raw_mode);
+    uint32_t baud_rate = mp_obj_get_int(raw_baud_rate);
+
+    void* handle = mp_flipper_uart_open(mode, baud_rate);
+
+    if(handle == NULL) {
+        mp_flipper_raise_os_error(MP_EBUSY);
+
+        return mp_const_none;
+    }
+
+    flipperzero_uart_connection_t* connection =
+        mp_obj_malloc_with_finaliser(flipperzero_uart_connection_t, &flipperzero_uart_connection_type);
+
+    connection->handle = handle;
+    connection->mode = raw_mode;
+    connection->baud_rate = raw_baud_rate;
+
+    return connection;
+}
+static MP_DEFINE_CONST_FUN_OBJ_2(flipperzero_uart_open_obj, flipperzero_uart_open);
+
+static mp_uint_t flipperzero_uart_read(mp_obj_t self, void* buf, mp_uint_t size, int* errcode) {
+    flipperzero_uart_connection_t* connection = MP_OBJ_TO_PTR(self);
+
+    if(connection->handle == NULL) {
+        *errcode = MP_EIO;
+
+        return MP_STREAM_ERROR;
+    }
+
+    return mp_flipper_uart_read(connection->handle, buf, size, errcode);
+}
+
+static mp_uint_t flipperzero_uart_write(mp_obj_t self, const void* buf, mp_uint_t size, int* errcode) {
+    flipperzero_uart_connection_t* connection = MP_OBJ_TO_PTR(self);
+
+    if(connection->handle == NULL) {
+        *errcode = MP_EIO;
+
+        return MP_STREAM_ERROR;
+    }
+
+    return mp_flipper_uart_write(connection->handle, buf, size, errcode);
+}
+
+static mp_uint_t flipperzero_uart_ioctl(mp_obj_t self, mp_uint_t request, uintptr_t arg, int* errcode) {
+    flipperzero_uart_connection_t* connection = MP_OBJ_TO_PTR(self);
+
+    if(connection->handle == NULL) {
+        return 0;
+    }
+
+    if(request == MP_STREAM_SEEK) {
+        return 0;
+    }
+
+    if(request == MP_STREAM_FLUSH) {
+        if(!mp_flipper_uart_sync(connection->handle)) {
+            *errcode = MP_EIO;
+
+            return MP_STREAM_ERROR;
+        }
+
+        return 0;
+    }
+
+    if(request == MP_STREAM_CLOSE) {
+        if(!mp_flipper_uart_close(connection->handle)) {
+            *errcode = MP_EIO;
+
+            connection->handle = NULL;
+
+            return MP_STREAM_ERROR;
+        }
+
+        connection->handle = NULL;
+
+        return 0;
+    }
+
+    *errcode = MP_EINVAL;
+
+    return MP_STREAM_ERROR;
+}
+
+static const mp_map_elem_t flipperzero_uart_connection_locals_dict_table[] = {
+    {MP_ROM_QSTR(MP_QSTR_read), MP_ROM_PTR(&mp_stream_read_obj)},
+    {MP_ROM_QSTR(MP_QSTR_readline), MP_ROM_PTR(&mp_stream_unbuffered_readline_obj)},
+    {MP_ROM_QSTR(MP_QSTR_readlines), MP_ROM_PTR(&mp_stream_unbuffered_readlines_obj)},
+    {MP_ROM_QSTR(MP_QSTR_write), MP_ROM_PTR(&mp_stream_write_obj)},
+    {MP_ROM_QSTR(MP_QSTR_flush), MP_ROM_PTR(&mp_stream_flush_obj)},
+    {MP_ROM_QSTR(MP_QSTR_close), MP_ROM_PTR(&mp_stream_close_obj)},
+    {MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&mp_stream_close_obj)},
+    {MP_ROM_QSTR(MP_QSTR___enter__), MP_ROM_PTR(&mp_identity_obj)},
+    {MP_ROM_QSTR(MP_QSTR___exit__), MP_ROM_PTR(&mp_stream___exit___obj)},
+};
+static MP_DEFINE_CONST_DICT(flipperzero_uart_connection_locals_dict, flipperzero_uart_connection_locals_dict_table);
+
+static const mp_stream_p_t flipperzero_uart_connection_stream_p = {
+    .read = flipperzero_uart_read,
+    .write = flipperzero_uart_write,
+    .ioctl = flipperzero_uart_ioctl,
+    .is_text = false,
+};
+
+MP_DEFINE_CONST_OBJ_TYPE(
+    flipperzero_uart_connection_type,
+    MP_QSTR_UART,
+    MP_TYPE_FLAG_ITER_IS_STREAM,
+    protocol,
+    &flipperzero_uart_connection_stream_p,
+    locals_dict,
+    &flipperzero_uart_connection_locals_dict);
+
 static const mp_rom_map_elem_t flipperzero_module_globals_table[] = {
 static const mp_rom_map_elem_t flipperzero_module_globals_table[] = {
     // light
     // light
     {MP_OBJ_NEW_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR_flipperzero)},
     {MP_OBJ_NEW_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR_flipperzero)},
@@ -846,6 +973,11 @@ for octave in range(9):
     {MP_ROM_QSTR(MP_QSTR_infrared_receive), MP_ROM_PTR(&flipperzero_infrared_receive_obj)},
     {MP_ROM_QSTR(MP_QSTR_infrared_receive), MP_ROM_PTR(&flipperzero_infrared_receive_obj)},
     {MP_ROM_QSTR(MP_QSTR_infrared_transmit), MP_ROM_PTR(&flipperzero_infrared_transmit_obj)},
     {MP_ROM_QSTR(MP_QSTR_infrared_transmit), MP_ROM_PTR(&flipperzero_infrared_transmit_obj)},
     {MP_ROM_QSTR(MP_QSTR_infrared_is_busy), MP_ROM_PTR(&flipperzero_infrared_is_busy_obj)},
     {MP_ROM_QSTR(MP_QSTR_infrared_is_busy), MP_ROM_PTR(&flipperzero_infrared_is_busy_obj)},
+    // UART
+    {MP_ROM_QSTR(MP_QSTR_UART), MP_ROM_PTR(&flipperzero_uart_connection_type)},
+    {MP_ROM_QSTR(MP_QSTR_UART_MODE_LPUART), MP_ROM_INT(MP_FLIPPER_UART_MODE_LPUART)},
+    {MP_ROM_QSTR(MP_QSTR_UART_MODE_USART), MP_ROM_INT(MP_FLIPPER_UART_MODE_USART)},
+    {MP_ROM_QSTR(MP_QSTR_uart_open), MP_ROM_PTR(&flipperzero_uart_open_obj)},
 };
 };
 static MP_DEFINE_CONST_DICT(flipperzero_module_globals, flipperzero_module_globals_table);
 static MP_DEFINE_CONST_DICT(flipperzero_module_globals, flipperzero_module_globals_table);
 
 

+ 9 - 0
lib/micropython/mp_flipper_modflipperzero.h

@@ -258,3 +258,12 @@ bool mp_flipper_infrared_transmit(
     float duty,
     float duty,
     bool use_external_pin);
     bool use_external_pin);
 bool mp_flipper_infrared_is_busy();
 bool mp_flipper_infrared_is_busy();
+
+#define MP_FLIPPER_UART_MODE_USART (0)
+#define MP_FLIPPER_UART_MODE_LPUART (1)
+
+void* mp_flipper_uart_open(uint8_t raw_mode, uint32_t baud_rate);
+bool mp_flipper_uart_close(void* handle);
+bool mp_flipper_uart_sync(void* handle);
+size_t mp_flipper_uart_read(void* handle, void* buffer, size_t size, int* errcode);
+size_t mp_flipper_uart_write(void* handle, const void* buffer, size_t size, int* errcode);

+ 12 - 0
lib/micropython/mp_flipper_repl.c

@@ -0,0 +1,12 @@
+#include "py/repl.h"
+#include "py/mpprint.h"
+
+#include "mp_flipper_repl.h"
+
+inline bool mp_flipper_repl_continue_with_input(const char* input) {
+    return mp_repl_continue_with_input(input);
+}
+
+inline size_t mp_flipper_repl_autocomplete(const char* str, size_t len, const mp_print_t* print, char** compl_str) {
+    return mp_repl_autocomplete(str, len, print, compl_str);
+}

+ 12 - 0
lib/micropython/mp_flipper_repl.h

@@ -0,0 +1,12 @@
+#pragma once
+
+#include <stddef.h>
+#include <stdbool.h>
+
+#include "py/mpprint.h"
+
+#include "mp_flipper_runtime.h"
+
+bool mp_flipper_repl_continue_with_input(const char* input);
+
+size_t mp_flipper_repl_autocomplete(const char* str, size_t len, const mp_print_t* print, char** compl_str);

+ 4 - 32
lib/micropython/mp_flipper_runtime.c

@@ -24,44 +24,16 @@ void mp_flipper_init(void* heap, size_t heap_size, size_t stack_size, void* stac
     mp_stack_set_top(stack_top);
     mp_stack_set_top(stack_top);
     mp_stack_set_limit(stack_size);
     mp_stack_set_limit(stack_size);
 
 
+#if MICROPY_ENABLE_GC
     gc_init(heap, (uint8_t*)heap + heap_size);
     gc_init(heap, (uint8_t*)heap + heap_size);
+#endif
 
 
     mp_init();
     mp_init();
 }
 }
 
 
-void mp_flipper_exec_mpy_file(const char* file_path) {
-#ifdef MP_FLIPPER_MPY_SUPPORT
-#if MP_FLIPPER_IS_RUNTIME
-    nlr_buf_t nlr;
-
-    if(nlr_push(&nlr) == 0) {
-        do {
-            // check if file exists
-            if(mp_flipper_import_stat(file_path) == MP_FLIPPER_IMPORT_STAT_NO_EXIST) {
-                mp_raise_OSError_with_filename(MP_ENOENT, file_path);
-
-                break;
-            }
-
-            // Execute the given .mpy file
-            mp_module_context_t* context = m_new_obj(mp_module_context_t);
-            context->module.globals = mp_globals_get();
-            mp_compiled_module_t compiled_module;
-            compiled_module.context = context;
-            mp_raw_code_load_file(qstr_from_str(file_path), &compiled_module);
-            mp_obj_t f = mp_make_function_from_proto_fun(compiled_module.rc, context, MP_OBJ_NULL);
-            mp_call_function_0(f);
-        } while(false);
-        nlr_pop();
-    } else {
-        // Uncaught exception: print it out.
-        mp_obj_print_exception(&mp_plat_print, (mp_obj_t)nlr.ret_val);
-    }
-#endif
-#endif
-}
-
 void mp_flipper_deinit() {
 void mp_flipper_deinit() {
+    gc_sweep_all();
+
     mp_deinit();
     mp_deinit();
 
 
     mp_flipper_context_free(mp_flipper_context);
     mp_flipper_context_free(mp_flipper_context);

+ 0 - 3
lib/micropython/mp_flipper_runtime.h

@@ -2,8 +2,6 @@
 
 
 #include <stddef.h>
 #include <stddef.h>
 
 
-#include "py/mperrno.h"
-
 #include "mpconfigport.h"
 #include "mpconfigport.h"
 
 
 extern const char* mp_flipper_root_module_path;
 extern const char* mp_flipper_root_module_path;
@@ -13,7 +11,6 @@ extern void* mp_flipper_context;
 void mp_flipper_set_root_module_path(const char* path);
 void mp_flipper_set_root_module_path(const char* path);
 
 
 void mp_flipper_init(void* memory, size_t memory_size, size_t stack_size, void* stack_top);
 void mp_flipper_init(void* memory, size_t memory_size, size_t stack_size, void* stack_top);
-void mp_flipper_exec_mpy_file(const char* file_path);
 void mp_flipper_save_file(const char* file_path, const char* data, size_t size);
 void mp_flipper_save_file(const char* file_path, const char* data, size_t size);
 void mp_flipper_deinit();
 void mp_flipper_deinit();
 void mp_flipper_nlr_jump_fail(void* value);
 void mp_flipper_nlr_jump_fail(void* value);

+ 162 - 5
lib/micropython/mpconfigport.h

@@ -1,7 +1,164 @@
-#pragma once
+// Need to provide a declaration/definition of alloca()
+#if defined(__FreeBSD__) || defined(__NetBSD__)
+#include <stdlib.h>
+#else
+#include <alloca.h>
+#endif
 
 
-#define MP_FLIPPER_COMPILER
-#define MP_FLIPPER_RUNTIME
-#define MP_FLIPPER_SPLIT_HEAP
+#include <stdint.h>
 
 
-#include "mp_flipper_config.h"
+// Type definitions for the specific machine
+typedef int32_t mp_int_t; // must be pointer size
+typedef uint32_t mp_uint_t; // must be pointer size
+typedef long mp_off_t;
+
+#define MICROPY_GC_SPLIT_HEAP (1)
+#define MICROPY_GC_SPLIT_HEAP_AUTO (1)
+
+#define MICROPY_MPHALPORT_H "mp_flipper_halport.h"
+
+#define MICROPY_MIN_USE_CORTEX_CPU (1)
+#define MICROPY_MIN_USE_STM32_MCU (1)
+
+#define MICROPY_HW_BOARD_NAME "Flipper Zero"
+#define MICROPY_HW_MCU_NAME "STM32WB55RG"
+
+#define MICROPY_CONFIG_ROM_LEVEL (MICROPY_CONFIG_ROM_LEVEL_CORE_FEATURES)
+
+#define MICROPY_EMIT_THUMB_ARMV7M (0)
+#define MICROPY_EMIT_INLINE_THUMB_FLOAT (0)
+
+#define MICROPY_PERSISTENT_CODE_LOAD (0)
+#define MICROPY_PERSISTENT_CODE_SAVE (0)
+#define MICROPY_PERSISTENT_CODE_SAVE_FILE (0)
+
+#define MICROPY_ENABLE_COMPILER (1)
+#define MICROPY_ENABLE_GC (1)
+#define MICROPY_PY_GC_COLLECT_RETVAL (0)
+#define MICROPY_ENABLE_PYSTACK (0)
+#define MICROPY_STACK_CHECK (0)
+#define MICROPY_ALLOC_PATH_MAX (128)
+
+#define MICROPY_ENABLE_FINALISER (1)
+
+#define MICROPY_ERROR_REPORTING (MICROPY_ERROR_REPORTING_NONE)
+
+#define MICROPY_GC_STACK_ENTRY_TYPE uint32_t
+
+#define MICROPY_PY___FILE__ (1)
+#define MICROPY_ENABLE_EXTERNAL_IMPORT (1)
+#define MICROPY_READER_VFS (1)
+
+#define MICROPY_ENABLE_VM_ABORT (0)
+
+#define MICROPY_PY_ERRNO (0)
+#define MICROPY_USE_INTERNAL_ERRNO (0)
+#define MICROPY_PY_ERRNO_ERRORCODE (0)
+
+#define MICROPY_PY_TIME (1)
+#define MICROPY_PY_TIME_TIME_TIME_NS (1)
+
+#define MICROPY_PY_RANDOM (1)
+#define MICROPY_PY_RANDOM_EXTRA_FUNCS (0)
+
+#define MICROPY_PY_RANDOM_SEED_INIT_FUNC (mp_flipper_seed_init())
+
+#define MICROPY_LONGINT_IMPL (MICROPY_LONGINT_IMPL_LONGLONG)
+
+#define MICROPY_FLOAT_IMPL (MICROPY_FLOAT_IMPL_FLOAT)
+
+#define MICROPY_ENABLE_SOURCE_LINE (0)
+#define MICROPY_ENABLE_DOC_STRING (0)
+
+#define MICROPY_HELPER_REPL (1)
+#define MICROPY_REPL_INFO (0)
+#define MICROPY_REPL_EMACS_KEYS (0)
+#define MICROPY_REPL_EMACS_WORDS_MOVE (0)
+#define MICROPY_REPL_EMACS_EXTRA_WORDS_MOVE (0)
+#define MICROPY_REPL_AUTO_INDENT (0)
+#define MICROPY_REPL_EVENT_DRIVEN (0)
+#define MICROPY_READLINE_HISTORY_SIZE (0)
+
+#define MICROPY_CPYTHON_COMPAT (1)
+#define MICROPY_FULL_CHECKS (0)
+#define MICROPY_BUILTIN_METHOD_CHECK_SELF_ARG (0)
+
+#define MICROPY_MODULE_FROZEN_MPY (0)
+
+#define MICROPY_PY_CMATH (0)
+#define MICROPY_PY_BUILTINS_COMPLEX (0)
+#define MICROPY_MULTIPLE_INHERITANCE (0)
+#define MICROPY_MODULE_GETATTR (0)
+#define MICROPY_PY_FUNCTION_ATTRS (1)
+#define MICROPY_PY_DESCRIPTORS (0)
+#define MICROPY_PY_ASYNC_AWAIT (0)
+#define MICROPY_PY_ASSIGN_EXPR (0)
+#define MICROPY_PY_GENERATOR_PEND_THROW (0)
+#define MICROPY_PY_BUILTINS_BYTES_HEX (0)
+#define MICROPY_PY_BUILTINS_STR_UNICODE (0)
+#define MICROPY_PY_BUILTINS_STR_CENTER (0)
+#define MICROPY_PY_BUILTINS_STR_COUNT (0)
+#define MICROPY_PY_BUILTINS_STR_OP_MODULO (1)
+#define MICROPY_PY_BUILTINS_STR_PARTITION (0)
+#define MICROPY_PY_BUILTINS_STR_SPLITLINES (0)
+#define MICROPY_PY_BUILTINS_BYTEARRAY (0)
+#define MICROPY_PY_BUILTINS_DICT_FROMKEYS (0)
+#define MICROPY_PY_BUILTINS_MEMORYVIEW (0)
+#define MICROPY_PY_BUILTINS_SET (1)
+#define MICROPY_PY_BUILTINS_SLICE (0)
+#define MICROPY_PY_BUILTINS_SLICE_ATTRS (0)
+#define MICROPY_PY_BUILTINS_SLICE_INDICES (0)
+#define MICROPY_PY_BUILTINS_FROZENSET (0)
+#define MICROPY_PY_BUILTINS_PROPERTY (0)
+#define MICROPY_PY_BUILTINS_RANGE_ATTRS (0)
+#define MICROPY_PY_BUILTINS_RANGE_BINOP (0)
+#define MICROPY_PY_BUILTINS_NEXT2 (0)
+#define MICROPY_PY_BUILTINS_ROUND_INT (0)
+#define MICROPY_PY_ALL_SPECIAL_METHODS (0)
+#define MICROPY_PY_REVERSE_SPECIAL_METHODS (0)
+#define MICROPY_PY_BUILTINS_ENUMERATE (0)
+#define MICROPY_PY_BUILTINS_COMPILE (0)
+#define MICROPY_PY_BUILTINS_EVAL_EXEC (0)
+#define MICROPY_PY_BUILTINS_EXECFILE (0)
+#define MICROPY_PY_BUILTINS_FILTER (1)
+#define MICROPY_PY_BUILTINS_REVERSED (1)
+#define MICROPY_PY_BUILTINS_NOTIMPLEMENTED (0)
+#define MICROPY_PY_BUILTINS_INPUT (0)
+#define MICROPY_PY_BUILTINS_MIN_MAX (1)
+#define MICROPY_PY_BUILTINS_POW3 (0)
+#define MICROPY_PY_BUILTINS_HELP (0)
+#define MICROPY_PY_MICROPYTHON (0)
+#define MICROPY_PY_MICROPYTHON_MEM_INFO (0)
+#define MICROPY_PY_MICROPYTHON_STACK_USE (0)
+#define MICROPY_PY_MICROPYTHON_HEAP_LOCKED (0)
+#define MICROPY_PY_ARRAY (0)
+#define MICROPY_PY_ARRAY_SLICE_ASSIGN (0)
+#define MICROPY_PY_ATTRTUPLE (0)
+#define MICROPY_PY_COLLECTIONS (0)
+#define MICROPY_PY_STRUCT (0)
+#define MICROPY_PY_GC (0)
+#define MICROPY_PY_SYS (0)
+#define MICROPY_PY_SYS_MODULES (0)
+#define MICROPY_PY_SELECT_SELECT (0)
+#define MICROPY_PY_SYS_EXIT (0)
+#define MICROPY_PY_RE (0)
+#define MICROPY_PY_CRYPTOLIB (0)
+#define MICROPY_PY_VFS (0)
+#define MICROPY_ENABLE_SCHEDULER (1)
+#define MICROPY_MODULE_BUILTIN_INIT (1)
+
+#define MICROPY_PY_MATH (0)
+
+#define MICROPY_PY_IO (0)
+#define MICROPY_PY_IO_BYTESIO (0)
+
+#define MICROPY_PY_JSON (0)
+#define MICROPY_PY_JSON_SEPARATORS (0)
+
+#define MICROPY_COMP_CONST_FOLDING (0)
+#define MICROPY_COMP_CONST_TUPLE (0)
+#define MICROPY_COMP_CONST_LITERAL (0)
+#define MICROPY_COMP_CONST (0)
+#define MICROPY_COMP_DOUBLE_TUPLE_ASSIGN (0)
+
+#define MICROPY_USE_INTERNAL_PRINTF (0)

+ 23 - 0
lib/micropython/py/dynruntime.h

@@ -88,8 +88,10 @@ static inline void *m_realloc_dyn(void *ptr, size_t new_num_bytes) {
 #define mp_type_int                         (*(mp_obj_type_t *)(mp_load_global(MP_QSTR_int)))
 #define mp_type_int                         (*(mp_obj_type_t *)(mp_load_global(MP_QSTR_int)))
 #define mp_type_str                         (*mp_fun_table.type_str)
 #define mp_type_str                         (*mp_fun_table.type_str)
 #define mp_type_bytes                       (*(mp_obj_type_t *)(mp_load_global(MP_QSTR_bytes)))
 #define mp_type_bytes                       (*(mp_obj_type_t *)(mp_load_global(MP_QSTR_bytes)))
+#define mp_type_bytearray                   (*(mp_obj_type_t *)(mp_load_global(MP_QSTR_bytearray)))
 #define mp_type_tuple                       (*((mp_obj_base_t *)mp_const_empty_tuple)->type)
 #define mp_type_tuple                       (*((mp_obj_base_t *)mp_const_empty_tuple)->type)
 #define mp_type_list                        (*mp_fun_table.type_list)
 #define mp_type_list                        (*mp_fun_table.type_list)
+#define mp_type_Exception                   (*mp_fun_table.type_Exception)
 #define mp_type_EOFError                    (*(mp_obj_type_t *)(mp_load_global(MP_QSTR_EOFError)))
 #define mp_type_EOFError                    (*(mp_obj_type_t *)(mp_load_global(MP_QSTR_EOFError)))
 #define mp_type_IndexError                  (*(mp_obj_type_t *)(mp_load_global(MP_QSTR_IndexError)))
 #define mp_type_IndexError                  (*(mp_obj_type_t *)(mp_load_global(MP_QSTR_IndexError)))
 #define mp_type_KeyError                    (*(mp_obj_type_t *)(mp_load_global(MP_QSTR_KeyError)))
 #define mp_type_KeyError                    (*(mp_obj_type_t *)(mp_load_global(MP_QSTR_KeyError)))
@@ -193,6 +195,7 @@ static inline void *mp_obj_malloc_helper_dyn(size_t num_bytes, const mp_obj_type
 #define mp_load_global(qst)               (mp_fun_table.load_global((qst)))
 #define mp_load_global(qst)               (mp_fun_table.load_global((qst)))
 #define mp_load_attr(base, attr)          (mp_fun_table.load_attr((base), (attr)))
 #define mp_load_attr(base, attr)          (mp_fun_table.load_attr((base), (attr)))
 #define mp_load_method(base, attr, dest)  (mp_fun_table.load_method((base), (attr), (dest)))
 #define mp_load_method(base, attr, dest)  (mp_fun_table.load_method((base), (attr), (dest)))
+#define mp_load_method_maybe(base, attr, dest) (mp_fun_table.load_method_maybe((base), (attr), (dest)))
 #define mp_load_super_method(attr, dest)  (mp_fun_table.load_super_method((attr), (dest)))
 #define mp_load_super_method(attr, dest)  (mp_fun_table.load_super_method((attr), (dest)))
 #define mp_store_name(qst, obj)           (mp_fun_table.store_name((qst), (obj)))
 #define mp_store_name(qst, obj)           (mp_fun_table.store_name((qst), (obj)))
 #define mp_store_global(qst, obj)         (mp_fun_table.store_global((qst), (obj)))
 #define mp_store_global(qst, obj)         (mp_fun_table.store_global((qst), (obj)))
@@ -210,6 +213,12 @@ static inline void *mp_obj_malloc_helper_dyn(size_t num_bytes, const mp_obj_type
 #define mp_arg_check_num(n_args, n_kw, n_args_min, n_args_max, takes_kw) \
 #define mp_arg_check_num(n_args, n_kw, n_args_min, n_args_max, takes_kw) \
     (mp_fun_table.arg_check_num_sig((n_args), (n_kw), MP_OBJ_FUN_MAKE_SIG((n_args_min), (n_args_max), (takes_kw))))
     (mp_fun_table.arg_check_num_sig((n_args), (n_kw), MP_OBJ_FUN_MAKE_SIG((n_args_min), (n_args_max), (takes_kw))))
 
 
+#define mp_arg_parse_all(n_pos, pos, kws, n_allowed, allowed, out_vals) \
+    (mp_fun_table.arg_parse_all((n_pos), (pos), (kws), (n_allowed), (allowed), (out_vals)))
+
+#define mp_arg_parse_all_kw_array(n_pos, n_kw, args, n_allowed, allowed, out_vals) \
+    (mp_fun_table.arg_parse_all_kw_array((n_pos), (n_kw), (args), (n_allowed), (allowed), (out_vals)))
+
 #define MP_DYNRUNTIME_INIT_ENTRY \
 #define MP_DYNRUNTIME_INIT_ENTRY \
     mp_obj_t old_globals = mp_fun_table.swap_globals(self->context->module.globals); \
     mp_obj_t old_globals = mp_fun_table.swap_globals(self->context->module.globals); \
     mp_raw_code_truncated_t rc; \
     mp_raw_code_truncated_t rc; \
@@ -236,6 +245,10 @@ static inline void *mp_obj_malloc_helper_dyn(size_t num_bytes, const mp_obj_type
 /******************************************************************************/
 /******************************************************************************/
 // Exceptions
 // Exceptions
 
 
+#define mp_obj_exception_make_new               (MP_OBJ_TYPE_GET_SLOT(&mp_type_Exception, make_new))
+#define mp_obj_exception_print                  (MP_OBJ_TYPE_GET_SLOT(&mp_type_Exception, print))
+#define mp_obj_exception_attr                   (MP_OBJ_TYPE_GET_SLOT(&mp_type_Exception, attr))
+
 #define mp_obj_new_exception(o)                 ((mp_obj_t)(o)) // Assumes returned object will be raised, will create instance then
 #define mp_obj_new_exception(o)                 ((mp_obj_t)(o)) // Assumes returned object will be raised, will create instance then
 #define mp_obj_new_exception_arg1(e_type, arg)  (mp_obj_new_exception_arg1_dyn((e_type), (arg)))
 #define mp_obj_new_exception_arg1(e_type, arg)  (mp_obj_new_exception_arg1_dyn((e_type), (arg)))
 
 
@@ -263,6 +276,16 @@ static inline void mp_raise_OSError_dyn(int er) {
     nlr_raise(mp_call_function_n_kw(mp_load_global(MP_QSTR_OSError), 1, 0, &args[0]));
     nlr_raise(mp_call_function_n_kw(mp_load_global(MP_QSTR_OSError), 1, 0, &args[0]));
 }
 }
 
 
+static inline void mp_obj_exception_init(mp_obj_full_type_t *exc, qstr name, const mp_obj_type_t *base) {
+    exc->base.type = &mp_type_type;
+    exc->flags = MP_TYPE_FLAG_NONE;
+    exc->name = name;
+    MP_OBJ_TYPE_SET_SLOT(exc, make_new, mp_obj_exception_make_new, 0);
+    MP_OBJ_TYPE_SET_SLOT(exc, print, mp_obj_exception_print, 1);
+    MP_OBJ_TYPE_SET_SLOT(exc, attr, mp_obj_exception_attr, 2);
+    MP_OBJ_TYPE_SET_SLOT(exc, parent, base, 3);
+}
+
 /******************************************************************************/
 /******************************************************************************/
 // Floating point
 // Floating point
 
 

+ 7 - 1
lib/micropython/py/mpconfig.h

@@ -32,7 +32,7 @@
 #define MICROPY_VERSION_MAJOR 1
 #define MICROPY_VERSION_MAJOR 1
 #define MICROPY_VERSION_MINOR 23
 #define MICROPY_VERSION_MINOR 23
 #define MICROPY_VERSION_MICRO 0
 #define MICROPY_VERSION_MICRO 0
-#define MICROPY_VERSION_PRERELEASE 1
+#define MICROPY_VERSION_PRERELEASE 0
 
 
 // Combined version as a 32-bit number for convenience to allow version
 // Combined version as a 32-bit number for convenience to allow version
 // comparison. Doesn't include prerelease state.
 // comparison. Doesn't include prerelease state.
@@ -587,6 +587,12 @@
 /*****************************************************************************/
 /*****************************************************************************/
 /* Python internal features                                                  */
 /* Python internal features                                                  */
 
 
+// Use a special long jump in nlrthumb.c, which may be necessary if nlr.o and
+// nlrthumb.o are linked far apart from each other.
+#ifndef MICROPY_NLR_THUMB_USE_LONG_JUMP
+#define MICROPY_NLR_THUMB_USE_LONG_JUMP (0)
+#endif
+
 // Whether to enable import of external modules
 // Whether to enable import of external modules
 // When disabled, only importing of built-in modules is supported
 // When disabled, only importing of built-in modules is supported
 // When enabled, a port must implement mp_import_stat (among other things)
 // When enabled, a port must implement mp_import_stat (among other things)

+ 4 - 0
lib/micropython/py/nativeglue.c

@@ -329,8 +329,11 @@ const mp_fun_table_t mp_fun_table = {
     mp_obj_new_float_from_d,
     mp_obj_new_float_from_d,
     mp_obj_get_float_to_f,
     mp_obj_get_float_to_f,
     mp_obj_get_float_to_d,
     mp_obj_get_float_to_d,
+    mp_load_method_maybe,
     mp_get_buffer,
     mp_get_buffer,
     mp_get_stream_raise,
     mp_get_stream_raise,
+    mp_arg_parse_all,
+    mp_arg_parse_all_kw_array,
     mp_binary_get_size,
     mp_binary_get_size,
     mp_binary_get_val_array,
     mp_binary_get_val_array,
     mp_binary_set_val_array,
     mp_binary_set_val_array,
@@ -344,6 +347,7 @@ const mp_fun_table_t mp_fun_table = {
     &mp_type_fun_builtin_2,
     &mp_type_fun_builtin_2,
     &mp_type_fun_builtin_3,
     &mp_type_fun_builtin_3,
     &mp_type_fun_builtin_var,
     &mp_type_fun_builtin_var,
+    &mp_type_Exception,
     &mp_stream_read_obj,
     &mp_stream_read_obj,
     &mp_stream_readinto_obj,
     &mp_stream_readinto_obj,
     &mp_stream_unbuffered_readline_obj,
     &mp_stream_unbuffered_readline_obj,

+ 5 - 1
lib/micropython/py/nativeglue.h

@@ -154,13 +154,16 @@ typedef struct _mp_fun_table_t {
     mp_obj_t (*obj_new_float_from_d)(double d);
     mp_obj_t (*obj_new_float_from_d)(double d);
     float (*obj_get_float_to_f)(mp_obj_t o);
     float (*obj_get_float_to_f)(mp_obj_t o);
     double (*obj_get_float_to_d)(mp_obj_t o);
     double (*obj_get_float_to_d)(mp_obj_t o);
+    void (*load_method_maybe)(mp_obj_t base, qstr attr, mp_obj_t *dest);
     bool (*get_buffer)(mp_obj_t obj, mp_buffer_info_t *bufinfo, mp_uint_t flags);
     bool (*get_buffer)(mp_obj_t obj, mp_buffer_info_t *bufinfo, mp_uint_t flags);
     const mp_stream_p_t *(*get_stream_raise)(mp_obj_t self_in, int flags);
     const mp_stream_p_t *(*get_stream_raise)(mp_obj_t self_in, int flags);
+    void (*arg_parse_all)(size_t n_pos, const mp_obj_t *pos, mp_map_t *kws, size_t n_allowed, const mp_arg_t *allowed, mp_arg_val_t *out_vals);
+    void (*arg_parse_all_kw_array)(size_t n_pos, size_t n_kw, const mp_obj_t *args, size_t n_allowed, const mp_arg_t *allowed, mp_arg_val_t *out_vals);
     size_t (*binary_get_size)(char struct_type, char val_type, size_t *palign);
     size_t (*binary_get_size)(char struct_type, char val_type, size_t *palign);
     mp_obj_t (*binary_get_val_array)(char typecode, void *p, size_t index);
     mp_obj_t (*binary_get_val_array)(char typecode, void *p, size_t index);
     void (*binary_set_val_array)(char typecode, void *p, size_t index, mp_obj_t val_in);
     void (*binary_set_val_array)(char typecode, void *p, size_t index, mp_obj_t val_in);
     const mp_print_t *plat_print;
     const mp_print_t *plat_print;
-    // The following entries start at index 70 and are referenced by tools-mpy_ld.py,
+    // The following entries start at index 73 and are referenced by tools-mpy_ld.py,
     // see constant MP_FUN_TABLE_MP_TYPE_TYPE_OFFSET.
     // see constant MP_FUN_TABLE_MP_TYPE_TYPE_OFFSET.
     const mp_obj_type_t *type_type;
     const mp_obj_type_t *type_type;
     const mp_obj_type_t *type_str;
     const mp_obj_type_t *type_str;
@@ -171,6 +174,7 @@ typedef struct _mp_fun_table_t {
     const mp_obj_type_t *type_fun_builtin_2;
     const mp_obj_type_t *type_fun_builtin_2;
     const mp_obj_type_t *type_fun_builtin_3;
     const mp_obj_type_t *type_fun_builtin_3;
     const mp_obj_type_t *type_fun_builtin_var;
     const mp_obj_type_t *type_fun_builtin_var;
+    const mp_obj_type_t *type_Exception;
     const mp_obj_fun_builtin_var_t *stream_read_obj;
     const mp_obj_fun_builtin_var_t *stream_read_obj;
     const mp_obj_fun_builtin_var_t *stream_readinto_obj;
     const mp_obj_fun_builtin_var_t *stream_readinto_obj;
     const mp_obj_fun_builtin_var_t *stream_unbuffered_readline_obj;
     const mp_obj_fun_builtin_var_t *stream_unbuffered_readline_obj;

+ 9 - 1
lib/micropython/py/nlrthumb.c

@@ -38,6 +38,14 @@
 
 
 __attribute__((naked)) unsigned int nlr_push(nlr_buf_t *nlr) {
 __attribute__((naked)) unsigned int nlr_push(nlr_buf_t *nlr) {
 
 
+    // If you get a linker error here, indicating that a relocation doesn't
+    // fit, try the following (in that order):
+    //
+    // 1. Ensure that nlr.o nlrthumb.o are linked closely together, i.e.
+    //    there aren't too many other files between them in the linker list
+    //    (PY_CORE_O_BASENAME in py/py.mk)
+    // 2. Set -DMICROPY_NLR_THUMB_USE_LONG_JUMP=1 during the build
+    //
     __asm volatile (
     __asm volatile (
         "str    r4, [r0, #12]       \n" // store r4 into nlr_buf
         "str    r4, [r0, #12]       \n" // store r4 into nlr_buf
         "str    r5, [r0, #16]       \n" // store r5 into nlr_buf
         "str    r5, [r0, #16]       \n" // store r5 into nlr_buf
@@ -71,7 +79,7 @@ __attribute__((naked)) unsigned int nlr_push(nlr_buf_t *nlr) {
         "str    lr, [r0, #8]        \n" // store lr into nlr_buf
         "str    lr, [r0, #8]        \n" // store lr into nlr_buf
         #endif
         #endif
 
 
-        #if !defined(__thumb2__)
+        #if MICROPY_NLR_THUMB_USE_LONG_JUMP
         "ldr    r1, nlr_push_tail_var \n"
         "ldr    r1, nlr_push_tail_var \n"
         "bx     r1                  \n" // do the rest in C
         "bx     r1                  \n" // do the rest in C
         ".align 2                   \n"
         ".align 2                   \n"

+ 14 - 14
lib/micropython/py/obj.h

@@ -753,20 +753,20 @@ typedef struct _mp_obj_full_type_t {
 // Do not use these directly, instead use MP_DEFINE_CONST_OBJ_TYPE.
 // Do not use these directly, instead use MP_DEFINE_CONST_OBJ_TYPE.
 // Generated with:
 // Generated with:
 // for i in range(13):
 // for i in range(13):
-//     print(f"#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_{i}(_struct_type, _typename, _name, _flags{''.join(f', f{j+1}, v{j+1}' for j in range(i))}) const _struct_type _typename = {{ .base = {{ &mp_type_type }}, .name = _name, .flags = _flags{''.join(f', .slot_index_##f{j+1} = {j+1}' for j in range(i))}{', .slots = { ' + ''.join(f'v{j+1}, ' for j in range(i)) + '}' if i else '' } }}")
-#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_0(_struct_type, _typename, _name, _flags) const _struct_type _typename = { .base = { &mp_type_type }, .name = _name, .flags = _flags }
-#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_1(_struct_type, _typename, _name, _flags, f1, v1) const _struct_type _typename = { .base = { &mp_type_type }, .name = _name, .flags = _flags, .slot_index_##f1 = 1, .slots = { v1, } }
-#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_2(_struct_type, _typename, _name, _flags, f1, v1, f2, v2) const _struct_type _typename = { .base = { &mp_type_type }, .name = _name, .flags = _flags, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slots = { v1, v2, } }
-#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_3(_struct_type, _typename, _name, _flags, f1, v1, f2, v2, f3, v3) const _struct_type _typename = { .base = { &mp_type_type }, .name = _name, .flags = _flags, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slot_index_##f3 = 3, .slots = { v1, v2, v3, } }
-#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_4(_struct_type, _typename, _name, _flags, f1, v1, f2, v2, f3, v3, f4, v4) const _struct_type _typename = { .base = { &mp_type_type }, .name = _name, .flags = _flags, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slot_index_##f3 = 3, .slot_index_##f4 = 4, .slots = { v1, v2, v3, v4, } }
-#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_5(_struct_type, _typename, _name, _flags, f1, v1, f2, v2, f3, v3, f4, v4, f5, v5) const _struct_type _typename = { .base = { &mp_type_type }, .name = _name, .flags = _flags, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slot_index_##f3 = 3, .slot_index_##f4 = 4, .slot_index_##f5 = 5, .slots = { v1, v2, v3, v4, v5, } }
-#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_6(_struct_type, _typename, _name, _flags, f1, v1, f2, v2, f3, v3, f4, v4, f5, v5, f6, v6) const _struct_type _typename = { .base = { &mp_type_type }, .name = _name, .flags = _flags, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slot_index_##f3 = 3, .slot_index_##f4 = 4, .slot_index_##f5 = 5, .slot_index_##f6 = 6, .slots = { v1, v2, v3, v4, v5, v6, } }
-#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_7(_struct_type, _typename, _name, _flags, f1, v1, f2, v2, f3, v3, f4, v4, f5, v5, f6, v6, f7, v7) const _struct_type _typename = { .base = { &mp_type_type }, .name = _name, .flags = _flags, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slot_index_##f3 = 3, .slot_index_##f4 = 4, .slot_index_##f5 = 5, .slot_index_##f6 = 6, .slot_index_##f7 = 7, .slots = { v1, v2, v3, v4, v5, v6, v7, } }
-#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_8(_struct_type, _typename, _name, _flags, f1, v1, f2, v2, f3, v3, f4, v4, f5, v5, f6, v6, f7, v7, f8, v8) const _struct_type _typename = { .base = { &mp_type_type }, .name = _name, .flags = _flags, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slot_index_##f3 = 3, .slot_index_##f4 = 4, .slot_index_##f5 = 5, .slot_index_##f6 = 6, .slot_index_##f7 = 7, .slot_index_##f8 = 8, .slots = { v1, v2, v3, v4, v5, v6, v7, v8, } }
-#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_9(_struct_type, _typename, _name, _flags, f1, v1, f2, v2, f3, v3, f4, v4, f5, v5, f6, v6, f7, v7, f8, v8, f9, v9) const _struct_type _typename = { .base = { &mp_type_type }, .name = _name, .flags = _flags, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slot_index_##f3 = 3, .slot_index_##f4 = 4, .slot_index_##f5 = 5, .slot_index_##f6 = 6, .slot_index_##f7 = 7, .slot_index_##f8 = 8, .slot_index_##f9 = 9, .slots = { v1, v2, v3, v4, v5, v6, v7, v8, v9, } }
-#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_10(_struct_type, _typename, _name, _flags, f1, v1, f2, v2, f3, v3, f4, v4, f5, v5, f6, v6, f7, v7, f8, v8, f9, v9, f10, v10) const _struct_type _typename = { .base = { &mp_type_type }, .name = _name, .flags = _flags, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slot_index_##f3 = 3, .slot_index_##f4 = 4, .slot_index_##f5 = 5, .slot_index_##f6 = 6, .slot_index_##f7 = 7, .slot_index_##f8 = 8, .slot_index_##f9 = 9, .slot_index_##f10 = 10, .slots = { v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, } }
-#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_11(_struct_type, _typename, _name, _flags, f1, v1, f2, v2, f3, v3, f4, v4, f5, v5, f6, v6, f7, v7, f8, v8, f9, v9, f10, v10, f11, v11) const _struct_type _typename = { .base = { &mp_type_type }, .name = _name, .flags = _flags, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slot_index_##f3 = 3, .slot_index_##f4 = 4, .slot_index_##f5 = 5, .slot_index_##f6 = 6, .slot_index_##f7 = 7, .slot_index_##f8 = 8, .slot_index_##f9 = 9, .slot_index_##f10 = 10, .slot_index_##f11 = 11, .slots = { v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, } }
-#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_12(_struct_type, _typename, _name, _flags, f1, v1, f2, v2, f3, v3, f4, v4, f5, v5, f6, v6, f7, v7, f8, v8, f9, v9, f10, v10, f11, v11, f12, v12) const _struct_type _typename = { .base = { &mp_type_type }, .name = _name, .flags = _flags, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slot_index_##f3 = 3, .slot_index_##f4 = 4, .slot_index_##f5 = 5, .slot_index_##f6 = 6, .slot_index_##f7 = 7, .slot_index_##f8 = 8, .slot_index_##f9 = 9, .slot_index_##f10 = 10, .slot_index_##f11 = 11, .slot_index_##f12 = 12, .slots = { v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, } }
+//     print(f"#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_{i}(_struct_type, _typename, _name, _flags{''.join(f', f{j+1}, v{j+1}' for j in range(i))}) const _struct_type _typename = {{ .base = {{ &mp_type_type }}, .flags = _flags, .name = _name{''.join(f', .slot_index_##f{j+1} = {j+1}' for j in range(i))}{', .slots = { ' + ''.join(f'v{j+1}, ' for j in range(i)) + '}' if i else '' } }}")
+#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_0(_struct_type, _typename, _name, _flags) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name }
+#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_1(_struct_type, _typename, _name, _flags, f1, v1) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slots = { v1, } }
+#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_2(_struct_type, _typename, _name, _flags, f1, v1, f2, v2) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slots = { v1, v2, } }
+#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_3(_struct_type, _typename, _name, _flags, f1, v1, f2, v2, f3, v3) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slot_index_##f3 = 3, .slots = { v1, v2, v3, } }
+#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_4(_struct_type, _typename, _name, _flags, f1, v1, f2, v2, f3, v3, f4, v4) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slot_index_##f3 = 3, .slot_index_##f4 = 4, .slots = { v1, v2, v3, v4, } }
+#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_5(_struct_type, _typename, _name, _flags, f1, v1, f2, v2, f3, v3, f4, v4, f5, v5) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slot_index_##f3 = 3, .slot_index_##f4 = 4, .slot_index_##f5 = 5, .slots = { v1, v2, v3, v4, v5, } }
+#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_6(_struct_type, _typename, _name, _flags, f1, v1, f2, v2, f3, v3, f4, v4, f5, v5, f6, v6) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slot_index_##f3 = 3, .slot_index_##f4 = 4, .slot_index_##f5 = 5, .slot_index_##f6 = 6, .slots = { v1, v2, v3, v4, v5, v6, } }
+#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_7(_struct_type, _typename, _name, _flags, f1, v1, f2, v2, f3, v3, f4, v4, f5, v5, f6, v6, f7, v7) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slot_index_##f3 = 3, .slot_index_##f4 = 4, .slot_index_##f5 = 5, .slot_index_##f6 = 6, .slot_index_##f7 = 7, .slots = { v1, v2, v3, v4, v5, v6, v7, } }
+#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_8(_struct_type, _typename, _name, _flags, f1, v1, f2, v2, f3, v3, f4, v4, f5, v5, f6, v6, f7, v7, f8, v8) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slot_index_##f3 = 3, .slot_index_##f4 = 4, .slot_index_##f5 = 5, .slot_index_##f6 = 6, .slot_index_##f7 = 7, .slot_index_##f8 = 8, .slots = { v1, v2, v3, v4, v5, v6, v7, v8, } }
+#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_9(_struct_type, _typename, _name, _flags, f1, v1, f2, v2, f3, v3, f4, v4, f5, v5, f6, v6, f7, v7, f8, v8, f9, v9) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slot_index_##f3 = 3, .slot_index_##f4 = 4, .slot_index_##f5 = 5, .slot_index_##f6 = 6, .slot_index_##f7 = 7, .slot_index_##f8 = 8, .slot_index_##f9 = 9, .slots = { v1, v2, v3, v4, v5, v6, v7, v8, v9, } }
+#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_10(_struct_type, _typename, _name, _flags, f1, v1, f2, v2, f3, v3, f4, v4, f5, v5, f6, v6, f7, v7, f8, v8, f9, v9, f10, v10) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slot_index_##f3 = 3, .slot_index_##f4 = 4, .slot_index_##f5 = 5, .slot_index_##f6 = 6, .slot_index_##f7 = 7, .slot_index_##f8 = 8, .slot_index_##f9 = 9, .slot_index_##f10 = 10, .slots = { v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, } }
+#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_11(_struct_type, _typename, _name, _flags, f1, v1, f2, v2, f3, v3, f4, v4, f5, v5, f6, v6, f7, v7, f8, v8, f9, v9, f10, v10, f11, v11) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slot_index_##f3 = 3, .slot_index_##f4 = 4, .slot_index_##f5 = 5, .slot_index_##f6 = 6, .slot_index_##f7 = 7, .slot_index_##f8 = 8, .slot_index_##f9 = 9, .slot_index_##f10 = 10, .slot_index_##f11 = 11, .slots = { v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, } }
+#define MP_DEFINE_CONST_OBJ_TYPE_NARGS_12(_struct_type, _typename, _name, _flags, f1, v1, f2, v2, f3, v3, f4, v4, f5, v5, f6, v6, f7, v7, f8, v8, f9, v9, f10, v10, f11, v11, f12, v12) const _struct_type _typename = { .base = { &mp_type_type }, .flags = _flags, .name = _name, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slot_index_##f3 = 3, .slot_index_##f4 = 4, .slot_index_##f5 = 5, .slot_index_##f6 = 6, .slot_index_##f7 = 7, .slot_index_##f8 = 8, .slot_index_##f9 = 9, .slot_index_##f10 = 10, .slot_index_##f11 = 11, .slot_index_##f12 = 12, .slots = { v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, } }
 
 
 // Because the mp_obj_type_t instances are in (zero-initialised) ROM, we take
 // Because the mp_obj_type_t instances are in (zero-initialised) ROM, we take
 // slot_index_foo=0 to mean that the slot is unset. This also simplifies checking
 // slot_index_foo=0 to mean that the slot is unset. This also simplifies checking

+ 16 - 4
lib/micropython/py/objarray.c

@@ -424,6 +424,13 @@ static mp_obj_t array_extend(mp_obj_t self_in, mp_obj_t arg_in) {
     if (self->free < len) {
     if (self->free < len) {
         self->items = m_renew(byte, self->items, (self->len + self->free) * sz, (self->len + len) * sz);
         self->items = m_renew(byte, self->items, (self->len + self->free) * sz, (self->len + len) * sz);
         self->free = 0;
         self->free = 0;
+
+        if (self_in == arg_in) {
+            // Get arg_bufinfo again in case self->items has moved
+            //
+            // (Note not possible to handle case that arg_in is a memoryview into self)
+            mp_get_buffer_raise(arg_in, &arg_bufinfo, MP_BUFFER_READ);
+        }
     } else {
     } else {
         self->free -= len;
         self->free -= len;
     }
     }
@@ -456,7 +463,8 @@ static mp_obj_t array_subscr(mp_obj_t self_in, mp_obj_t index_in, mp_obj_t value
                 #if MICROPY_PY_ARRAY_SLICE_ASSIGN
                 #if MICROPY_PY_ARRAY_SLICE_ASSIGN
                 // Assign
                 // Assign
                 size_t src_len;
                 size_t src_len;
-                void *src_items;
+                uint8_t *src_items;
+                size_t src_offs = 0;
                 size_t item_sz = mp_binary_get_size('@', o->typecode & TYPECODE_MASK, NULL);
                 size_t item_sz = mp_binary_get_size('@', o->typecode & TYPECODE_MASK, NULL);
                 if (mp_obj_is_obj(value) && MP_OBJ_TYPE_GET_SLOT_OR_NULL(((mp_obj_base_t *)MP_OBJ_TO_PTR(value))->type, subscr) == array_subscr) {
                 if (mp_obj_is_obj(value) && MP_OBJ_TYPE_GET_SLOT_OR_NULL(((mp_obj_base_t *)MP_OBJ_TO_PTR(value))->type, subscr) == array_subscr) {
                     // value is array, bytearray or memoryview
                     // value is array, bytearray or memoryview
@@ -469,7 +477,7 @@ static mp_obj_t array_subscr(mp_obj_t self_in, mp_obj_t index_in, mp_obj_t value
                     src_items = src_slice->items;
                     src_items = src_slice->items;
                     #if MICROPY_PY_BUILTINS_MEMORYVIEW
                     #if MICROPY_PY_BUILTINS_MEMORYVIEW
                     if (mp_obj_is_type(value, &mp_type_memoryview)) {
                     if (mp_obj_is_type(value, &mp_type_memoryview)) {
-                        src_items = (uint8_t *)src_items + (src_slice->memview_offset * item_sz);
+                        src_offs = src_slice->memview_offset * item_sz;
                     }
                     }
                     #endif
                     #endif
                 } else if (mp_obj_is_type(value, &mp_type_bytes)) {
                 } else if (mp_obj_is_type(value, &mp_type_bytes)) {
@@ -504,13 +512,17 @@ static mp_obj_t array_subscr(mp_obj_t self_in, mp_obj_t index_in, mp_obj_t value
                         // TODO: alloc policy; at the moment we go conservative
                         // TODO: alloc policy; at the moment we go conservative
                         o->items = m_renew(byte, o->items, (o->len + o->free) * item_sz, (o->len + len_adj) * item_sz);
                         o->items = m_renew(byte, o->items, (o->len + o->free) * item_sz, (o->len + len_adj) * item_sz);
                         o->free = len_adj;
                         o->free = len_adj;
+                        // m_renew may have moved o->items
+                        if (src_items == dest_items) {
+                            src_items = o->items;
+                        }
                         dest_items = o->items;
                         dest_items = o->items;
                     }
                     }
                     mp_seq_replace_slice_grow_inplace(dest_items, o->len,
                     mp_seq_replace_slice_grow_inplace(dest_items, o->len,
-                        slice.start, slice.stop, src_items, src_len, len_adj, item_sz);
+                        slice.start, slice.stop, src_items + src_offs, src_len, len_adj, item_sz);
                 } else {
                 } else {
                     mp_seq_replace_slice_no_grow(dest_items, o->len,
                     mp_seq_replace_slice_no_grow(dest_items, o->len,
-                        slice.start, slice.stop, src_items, src_len, item_sz);
+                        slice.start, slice.stop, src_items + src_offs, src_len, item_sz);
                     // Clear "freed" elements at the end of list
                     // Clear "freed" elements at the end of list
                     // TODO: This is actually only needed for typecode=='O'
                     // TODO: This is actually only needed for typecode=='O'
                     mp_seq_clear(dest_items, o->len + len_adj, o->len, item_sz);
                     mp_seq_clear(dest_items, o->len + len_adj, o->len, item_sz);

+ 4 - 4
lib/micropython/py/objfun.h

@@ -56,14 +56,14 @@ void mp_obj_fun_bc_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest);
 #if MICROPY_EMIT_NATIVE
 #if MICROPY_EMIT_NATIVE
 
 
 static inline mp_obj_t mp_obj_new_fun_native(const mp_obj_t *def_args, const void *fun_data, const mp_module_context_t *mc, struct _mp_raw_code_t *const *child_table) {
 static inline mp_obj_t mp_obj_new_fun_native(const mp_obj_t *def_args, const void *fun_data, const mp_module_context_t *mc, struct _mp_raw_code_t *const *child_table) {
-    mp_obj_fun_bc_t *o = MP_OBJ_TO_PTR(mp_obj_new_fun_bc(def_args, (const byte *)fun_data, mc, child_table));
+    mp_obj_fun_bc_t *o = (mp_obj_fun_bc_t *)MP_OBJ_TO_PTR(mp_obj_new_fun_bc(def_args, (const byte *)fun_data, mc, child_table));
     o->base.type = &mp_type_fun_native;
     o->base.type = &mp_type_fun_native;
     return MP_OBJ_FROM_PTR(o);
     return MP_OBJ_FROM_PTR(o);
 }
 }
 
 
 static inline mp_obj_t mp_obj_new_fun_viper(const void *fun_data, const mp_module_context_t *mc, struct _mp_raw_code_t *const *child_table) {
 static inline mp_obj_t mp_obj_new_fun_viper(const void *fun_data, const mp_module_context_t *mc, struct _mp_raw_code_t *const *child_table) {
     mp_obj_fun_bc_t *o = mp_obj_malloc(mp_obj_fun_bc_t, &mp_type_fun_viper);
     mp_obj_fun_bc_t *o = mp_obj_malloc(mp_obj_fun_bc_t, &mp_type_fun_viper);
-    o->bytecode = fun_data;
+    o->bytecode = (const byte *)fun_data;
     o->context = mc;
     o->context = mc;
     o->child_table = child_table;
     o->child_table = child_table;
     return MP_OBJ_FROM_PTR(o);
     return MP_OBJ_FROM_PTR(o);
@@ -101,9 +101,9 @@ static inline void *mp_obj_fun_native_get_generator_resume(const mp_obj_fun_bc_t
 
 
 #if MICROPY_EMIT_INLINE_ASM
 #if MICROPY_EMIT_INLINE_ASM
 static inline mp_obj_t mp_obj_new_fun_asm(size_t n_args, const void *fun_data, mp_uint_t type_sig) {
 static inline mp_obj_t mp_obj_new_fun_asm(size_t n_args, const void *fun_data, mp_uint_t type_sig) {
-    mp_obj_fun_asm_t *o = mp_obj_malloc(mp_obj_fun_asm_t, &mp_type_fun_asm);
+    mp_obj_fun_asm_t *o = (mp_obj_fun_asm_t *)mp_obj_malloc(mp_obj_fun_asm_t, &mp_type_fun_asm);
     o->n_args = n_args;
     o->n_args = n_args;
-    o->fun_data = fun_data;
+    o->fun_data = (const byte *)fun_data;
     o->type_sig = type_sig;
     o->type_sig = type_sig;
     return MP_OBJ_FROM_PTR(o);
     return MP_OBJ_FROM_PTR(o);
 }
 }

+ 2185 - 0
package-lock.json

@@ -0,0 +1,2185 @@
+{
+  "name": "mp-flipper-fap",
+  "lockfileVersion": 3,
+  "requires": true,
+  "packages": {
+    "": {
+      "dependencies": {
+        "terminalizer": "^0.12.0"
+      }
+    },
+    "node_modules/@electron/get": {
+      "version": "2.0.3",
+      "resolved": "https://registry.npmjs.org/@electron/get/-/get-2.0.3.tgz",
+      "integrity": "sha512-Qkzpg2s9GnVV2I2BjRksUi43U5e6+zaQMcjoJy0C+C5oxaKl+fmckGDQFtRpZpZV0NQekuZZ+tGz7EA9TVnQtQ==",
+      "license": "MIT",
+      "dependencies": {
+        "debug": "^4.1.1",
+        "env-paths": "^2.2.0",
+        "fs-extra": "^8.1.0",
+        "got": "^11.8.5",
+        "progress": "^2.0.3",
+        "semver": "^6.2.0",
+        "sumchecker": "^3.0.1"
+      },
+      "engines": {
+        "node": ">=12"
+      },
+      "optionalDependencies": {
+        "global-agent": "^3.0.0"
+      }
+    },
+    "node_modules/@electron/get/node_modules/fs-extra": {
+      "version": "8.1.0",
+      "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
+      "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==",
+      "license": "MIT",
+      "dependencies": {
+        "graceful-fs": "^4.2.0",
+        "jsonfile": "^4.0.0",
+        "universalify": "^0.1.0"
+      },
+      "engines": {
+        "node": ">=6 <7 || >=8"
+      }
+    },
+    "node_modules/@homebridge/node-pty-prebuilt-multiarch": {
+      "version": "0.11.14",
+      "resolved": "https://registry.npmjs.org/@homebridge/node-pty-prebuilt-multiarch/-/node-pty-prebuilt-multiarch-0.11.14.tgz",
+      "integrity": "sha512-fuiq5kb4i0Ao0BTf7O6kvtwUhCCCJHLhWLWaaUaLuniDGS4xmj+gxvkidJpxYVT/zTXdbcLuCY44UnoWC7xODg==",
+      "hasInstallScript": true,
+      "license": "MIT",
+      "dependencies": {
+        "nan": "^2.19.0",
+        "prebuild-install": "^7.1.2"
+      }
+    },
+    "node_modules/@sindresorhus/is": {
+      "version": "4.6.0",
+      "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz",
+      "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sindresorhus/is?sponsor=1"
+      }
+    },
+    "node_modules/@szmarczak/http-timer": {
+      "version": "4.0.6",
+      "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz",
+      "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==",
+      "license": "MIT",
+      "dependencies": {
+        "defer-to-connect": "^2.0.0"
+      },
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/@types/cacheable-request": {
+      "version": "6.0.3",
+      "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz",
+      "integrity": "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/http-cache-semantics": "*",
+        "@types/keyv": "^3.1.4",
+        "@types/node": "*",
+        "@types/responselike": "^1.0.0"
+      }
+    },
+    "node_modules/@types/http-cache-semantics": {
+      "version": "4.0.4",
+      "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz",
+      "integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==",
+      "license": "MIT"
+    },
+    "node_modules/@types/keyv": {
+      "version": "3.1.4",
+      "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz",
+      "integrity": "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/node": "*"
+      }
+    },
+    "node_modules/@types/node": {
+      "version": "18.19.54",
+      "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.54.tgz",
+      "integrity": "sha512-+BRgt0G5gYjTvdLac9sIeE0iZcJxi4Jc4PV5EUzqi+88jmQLr+fRZdv2tCTV7IHKSGxM6SaLoOXQWWUiLUItMw==",
+      "license": "MIT",
+      "dependencies": {
+        "undici-types": "~5.26.4"
+      }
+    },
+    "node_modules/@types/responselike": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz",
+      "integrity": "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/node": "*"
+      }
+    },
+    "node_modules/@types/yauzl": {
+      "version": "2.10.3",
+      "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz",
+      "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==",
+      "license": "MIT",
+      "optional": true,
+      "dependencies": {
+        "@types/node": "*"
+      }
+    },
+    "node_modules/ansi-escapes": {
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz",
+      "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/ansi-regex": {
+      "version": "4.1.1",
+      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz",
+      "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/ansi-styles": {
+      "version": "3.2.1",
+      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+      "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+      "license": "MIT",
+      "dependencies": {
+        "color-convert": "^1.9.0"
+      },
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/argparse": {
+      "version": "1.0.10",
+      "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
+      "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
+      "license": "MIT",
+      "dependencies": {
+        "sprintf-js": "~1.0.2"
+      }
+    },
+    "node_modules/async": {
+      "version": "2.6.4",
+      "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz",
+      "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==",
+      "license": "MIT",
+      "dependencies": {
+        "lodash": "^4.17.14"
+      }
+    },
+    "node_modules/async-promises": {
+      "version": "0.2.3",
+      "resolved": "https://registry.npmjs.org/async-promises/-/async-promises-0.2.3.tgz",
+      "integrity": "sha512-/iraEWVBoP+CTSonpypBsELpabsz8sWKq9XMfVzcrxwJ8Io6aooca9uH8kJDYgQF7jiwKY01itNoyqiyto9RLw==",
+      "license": "MIT"
+    },
+    "node_modules/asynckit": {
+      "version": "0.4.0",
+      "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+      "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
+      "license": "MIT"
+    },
+    "node_modules/axios": {
+      "version": "1.7.7",
+      "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz",
+      "integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==",
+      "license": "MIT",
+      "dependencies": {
+        "follow-redirects": "^1.15.6",
+        "form-data": "^4.0.0",
+        "proxy-from-env": "^1.1.0"
+      }
+    },
+    "node_modules/base64-js": {
+      "version": "1.5.1",
+      "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
+      "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/feross"
+        },
+        {
+          "type": "patreon",
+          "url": "https://www.patreon.com/feross"
+        },
+        {
+          "type": "consulting",
+          "url": "https://feross.org/support"
+        }
+      ],
+      "license": "MIT"
+    },
+    "node_modules/bl": {
+      "version": "4.1.0",
+      "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz",
+      "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==",
+      "license": "MIT",
+      "dependencies": {
+        "buffer": "^5.5.0",
+        "inherits": "^2.0.4",
+        "readable-stream": "^3.4.0"
+      }
+    },
+    "node_modules/bl/node_modules/readable-stream": {
+      "version": "3.6.2",
+      "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
+      "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
+      "license": "MIT",
+      "dependencies": {
+        "inherits": "^2.0.3",
+        "string_decoder": "^1.1.1",
+        "util-deprecate": "^1.0.1"
+      },
+      "engines": {
+        "node": ">= 6"
+      }
+    },
+    "node_modules/bl/node_modules/string_decoder": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
+      "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
+      "license": "MIT",
+      "dependencies": {
+        "safe-buffer": "~5.2.0"
+      }
+    },
+    "node_modules/boolean": {
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/boolean/-/boolean-3.2.0.tgz",
+      "integrity": "sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==",
+      "license": "MIT",
+      "optional": true
+    },
+    "node_modules/buffer": {
+      "version": "5.7.1",
+      "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
+      "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/feross"
+        },
+        {
+          "type": "patreon",
+          "url": "https://www.patreon.com/feross"
+        },
+        {
+          "type": "consulting",
+          "url": "https://feross.org/support"
+        }
+      ],
+      "license": "MIT",
+      "dependencies": {
+        "base64-js": "^1.3.1",
+        "ieee754": "^1.1.13"
+      }
+    },
+    "node_modules/buffer-crc32": {
+      "version": "0.2.13",
+      "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz",
+      "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==",
+      "license": "MIT",
+      "engines": {
+        "node": "*"
+      }
+    },
+    "node_modules/cacheable-lookup": {
+      "version": "5.0.4",
+      "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz",
+      "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=10.6.0"
+      }
+    },
+    "node_modules/cacheable-request": {
+      "version": "7.0.4",
+      "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.4.tgz",
+      "integrity": "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==",
+      "license": "MIT",
+      "dependencies": {
+        "clone-response": "^1.0.2",
+        "get-stream": "^5.1.0",
+        "http-cache-semantics": "^4.0.0",
+        "keyv": "^4.0.0",
+        "lowercase-keys": "^2.0.0",
+        "normalize-url": "^6.0.1",
+        "responselike": "^2.0.0"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/chalk": {
+      "version": "2.4.2",
+      "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+      "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+      "license": "MIT",
+      "dependencies": {
+        "ansi-styles": "^3.2.1",
+        "escape-string-regexp": "^1.0.5",
+        "supports-color": "^5.3.0"
+      },
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/chardet": {
+      "version": "0.7.0",
+      "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz",
+      "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==",
+      "license": "MIT"
+    },
+    "node_modules/chownr": {
+      "version": "1.1.4",
+      "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
+      "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==",
+      "license": "ISC"
+    },
+    "node_modules/cli-cursor": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz",
+      "integrity": "sha512-8lgKz8LmCRYZZQDpRyT2m5rKJ08TnU4tR9FFFW2rxpxR1FzWi4PQ/NfyODchAatHaUgnSPVcx/R5w6NuTBzFiw==",
+      "license": "MIT",
+      "dependencies": {
+        "restore-cursor": "^2.0.0"
+      },
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/cli-width": {
+      "version": "2.2.1",
+      "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.1.tgz",
+      "integrity": "sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw==",
+      "license": "ISC"
+    },
+    "node_modules/cliui": {
+      "version": "8.0.1",
+      "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
+      "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
+      "license": "ISC",
+      "dependencies": {
+        "string-width": "^4.2.0",
+        "strip-ansi": "^6.0.1",
+        "wrap-ansi": "^7.0.0"
+      },
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/cliui/node_modules/ansi-regex": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+      "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/cliui/node_modules/is-fullwidth-code-point": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+      "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/cliui/node_modules/string-width": {
+      "version": "4.2.3",
+      "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+      "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+      "license": "MIT",
+      "dependencies": {
+        "emoji-regex": "^8.0.0",
+        "is-fullwidth-code-point": "^3.0.0",
+        "strip-ansi": "^6.0.1"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/cliui/node_modules/strip-ansi": {
+      "version": "6.0.1",
+      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+      "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+      "license": "MIT",
+      "dependencies": {
+        "ansi-regex": "^5.0.1"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/clone-response": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz",
+      "integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==",
+      "license": "MIT",
+      "dependencies": {
+        "mimic-response": "^1.0.0"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/color-convert": {
+      "version": "1.9.3",
+      "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
+      "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
+      "license": "MIT",
+      "dependencies": {
+        "color-name": "1.1.3"
+      }
+    },
+    "node_modules/color-name": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
+      "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
+      "license": "MIT"
+    },
+    "node_modules/combined-stream": {
+      "version": "1.0.8",
+      "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+      "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+      "license": "MIT",
+      "dependencies": {
+        "delayed-stream": "~1.0.0"
+      },
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/core-util-is": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
+      "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==",
+      "license": "MIT"
+    },
+    "node_modules/death": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/death/-/death-1.1.0.tgz",
+      "integrity": "sha512-vsV6S4KVHvTGxbEcij7hkWRv0It+sGGWVOM67dQde/o5Xjnr+KmLjxWJii2uEObIrt1CcM9w0Yaovx+iOlIL+w=="
+    },
+    "node_modules/debug": {
+      "version": "4.3.7",
+      "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
+      "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
+      "license": "MIT",
+      "dependencies": {
+        "ms": "^2.1.3"
+      },
+      "engines": {
+        "node": ">=6.0"
+      },
+      "peerDependenciesMeta": {
+        "supports-color": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/decompress-response": {
+      "version": "6.0.0",
+      "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz",
+      "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==",
+      "license": "MIT",
+      "dependencies": {
+        "mimic-response": "^3.1.0"
+      },
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/decompress-response/node_modules/mimic-response": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz",
+      "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/deep-extend": {
+      "version": "0.6.0",
+      "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
+      "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=4.0.0"
+      }
+    },
+    "node_modules/deepmerge": {
+      "version": "2.2.1",
+      "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-2.2.1.tgz",
+      "integrity": "sha512-R9hc1Xa/NOBi9WRVUWg19rl1UB7Tt4kuPd+thNJgFZoxXsTz7ncaPaeIm+40oSGuP33DfMb4sZt1QIGiJzC4EA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/defer-to-connect": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz",
+      "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/define-data-property": {
+      "version": "1.1.4",
+      "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
+      "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
+      "license": "MIT",
+      "optional": true,
+      "dependencies": {
+        "es-define-property": "^1.0.0",
+        "es-errors": "^1.3.0",
+        "gopd": "^1.0.1"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/define-properties": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz",
+      "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==",
+      "license": "MIT",
+      "optional": true,
+      "dependencies": {
+        "define-data-property": "^1.0.1",
+        "has-property-descriptors": "^1.0.0",
+        "object-keys": "^1.1.1"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/delayed-stream": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+      "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.4.0"
+      }
+    },
+    "node_modules/detect-libc": {
+      "version": "2.0.3",
+      "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz",
+      "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==",
+      "license": "Apache-2.0",
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/detect-node": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz",
+      "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==",
+      "license": "MIT",
+      "optional": true
+    },
+    "node_modules/electron": {
+      "version": "25.9.8",
+      "resolved": "https://registry.npmjs.org/electron/-/electron-25.9.8.tgz",
+      "integrity": "sha512-PGgp6PH46QVENHuAHc2NT1Su8Q1qov7qIl2jI5tsDpTibwV2zD8539AeWBQySeBU4dhbj9onIl7+1bXQ0wefBg==",
+      "hasInstallScript": true,
+      "license": "MIT",
+      "dependencies": {
+        "@electron/get": "^2.0.0",
+        "@types/node": "^18.11.18",
+        "extract-zip": "^2.0.1"
+      },
+      "bin": {
+        "electron": "cli.js"
+      },
+      "engines": {
+        "node": ">= 12.20.55"
+      }
+    },
+    "node_modules/emoji-regex": {
+      "version": "8.0.0",
+      "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+      "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+      "license": "MIT"
+    },
+    "node_modules/end-of-stream": {
+      "version": "1.4.4",
+      "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
+      "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==",
+      "license": "MIT",
+      "dependencies": {
+        "once": "^1.4.0"
+      }
+    },
+    "node_modules/env-paths": {
+      "version": "2.2.1",
+      "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz",
+      "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/es-define-property": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz",
+      "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==",
+      "license": "MIT",
+      "optional": true,
+      "dependencies": {
+        "get-intrinsic": "^1.2.4"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/es-errors": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
+      "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+      "license": "MIT",
+      "optional": true,
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/es6-error": {
+      "version": "4.1.1",
+      "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz",
+      "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==",
+      "license": "MIT",
+      "optional": true
+    },
+    "node_modules/escalade": {
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
+      "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/escape-string-regexp": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+      "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.8.0"
+      }
+    },
+    "node_modules/esprima": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
+      "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
+      "license": "BSD-2-Clause",
+      "bin": {
+        "esparse": "bin/esparse.js",
+        "esvalidate": "bin/esvalidate.js"
+      },
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/expand-template": {
+      "version": "2.0.3",
+      "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz",
+      "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==",
+      "license": "(MIT OR WTFPL)",
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/external-editor": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz",
+      "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==",
+      "license": "MIT",
+      "dependencies": {
+        "chardet": "^0.7.0",
+        "iconv-lite": "^0.4.24",
+        "tmp": "^0.0.33"
+      },
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/external-editor/node_modules/tmp": {
+      "version": "0.0.33",
+      "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
+      "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==",
+      "license": "MIT",
+      "dependencies": {
+        "os-tmpdir": "~1.0.2"
+      },
+      "engines": {
+        "node": ">=0.6.0"
+      }
+    },
+    "node_modules/extract-zip": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz",
+      "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==",
+      "license": "BSD-2-Clause",
+      "dependencies": {
+        "debug": "^4.1.1",
+        "get-stream": "^5.1.0",
+        "yauzl": "^2.10.0"
+      },
+      "bin": {
+        "extract-zip": "cli.js"
+      },
+      "engines": {
+        "node": ">= 10.17.0"
+      },
+      "optionalDependencies": {
+        "@types/yauzl": "^2.9.1"
+      }
+    },
+    "node_modules/fd-slicer": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz",
+      "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==",
+      "license": "MIT",
+      "dependencies": {
+        "pend": "~1.2.0"
+      }
+    },
+    "node_modules/figures": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz",
+      "integrity": "sha512-Oa2M9atig69ZkfwiApY8F2Yy+tzMbazyvqv21R0NsSC8floSOC09BbT1ITWAdoMGQvJ/aZnR1KMwdx9tvHnTNA==",
+      "license": "MIT",
+      "dependencies": {
+        "escape-string-regexp": "^1.0.5"
+      },
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/follow-redirects": {
+      "version": "1.15.9",
+      "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
+      "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
+      "funding": [
+        {
+          "type": "individual",
+          "url": "https://github.com/sponsors/RubenVerborgh"
+        }
+      ],
+      "license": "MIT",
+      "engines": {
+        "node": ">=4.0"
+      },
+      "peerDependenciesMeta": {
+        "debug": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/form-data": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
+      "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
+      "license": "MIT",
+      "dependencies": {
+        "asynckit": "^0.4.0",
+        "combined-stream": "^1.0.8",
+        "mime-types": "^2.1.12"
+      },
+      "engines": {
+        "node": ">= 6"
+      }
+    },
+    "node_modules/fs-constants": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
+      "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==",
+      "license": "MIT"
+    },
+    "node_modules/fs-extra": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-5.0.0.tgz",
+      "integrity": "sha512-66Pm4RYbjzdyeuqudYqhFiNBbCIuI9kgRqLPSHIlXHidW8NIQtVdkM1yeZ4lXwuhbTETv3EUGMNHAAw6hiundQ==",
+      "license": "MIT",
+      "dependencies": {
+        "graceful-fs": "^4.1.2",
+        "jsonfile": "^4.0.0",
+        "universalify": "^0.1.0"
+      }
+    },
+    "node_modules/function-bind": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+      "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+      "license": "MIT",
+      "optional": true,
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/get-caller-file": {
+      "version": "2.0.5",
+      "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
+      "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
+      "license": "ISC",
+      "engines": {
+        "node": "6.* || 8.* || >= 10.*"
+      }
+    },
+    "node_modules/get-intrinsic": {
+      "version": "1.2.4",
+      "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz",
+      "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==",
+      "license": "MIT",
+      "optional": true,
+      "dependencies": {
+        "es-errors": "^1.3.0",
+        "function-bind": "^1.1.2",
+        "has-proto": "^1.0.1",
+        "has-symbols": "^1.0.3",
+        "hasown": "^2.0.0"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/get-stream": {
+      "version": "5.2.0",
+      "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz",
+      "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==",
+      "license": "MIT",
+      "dependencies": {
+        "pump": "^3.0.0"
+      },
+      "engines": {
+        "node": ">=8"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/gif-encoder": {
+      "version": "0.6.1",
+      "resolved": "https://registry.npmjs.org/gif-encoder/-/gif-encoder-0.6.1.tgz",
+      "integrity": "sha512-pDHqEc0/F+9RvJwah0TkZbE+FvvYdjvopES2cBvzaVqi0H5ShvmQxYkohN7fvhktPuGQ9LlkOqolHjA7tJ11qg==",
+      "dependencies": {
+        "readable-stream": "~1.1.9"
+      },
+      "engines": {
+        "node": ">= 0.10.0"
+      }
+    },
+    "node_modules/github-from-package": {
+      "version": "0.0.0",
+      "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz",
+      "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==",
+      "license": "MIT"
+    },
+    "node_modules/global-agent": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/global-agent/-/global-agent-3.0.0.tgz",
+      "integrity": "sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==",
+      "license": "BSD-3-Clause",
+      "optional": true,
+      "dependencies": {
+        "boolean": "^3.0.1",
+        "es6-error": "^4.1.1",
+        "matcher": "^3.0.0",
+        "roarr": "^2.15.3",
+        "semver": "^7.3.2",
+        "serialize-error": "^7.0.1"
+      },
+      "engines": {
+        "node": ">=10.0"
+      }
+    },
+    "node_modules/global-agent/node_modules/semver": {
+      "version": "7.6.3",
+      "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
+      "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
+      "license": "ISC",
+      "optional": true,
+      "bin": {
+        "semver": "bin/semver.js"
+      },
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/globalthis": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz",
+      "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==",
+      "license": "MIT",
+      "optional": true,
+      "dependencies": {
+        "define-properties": "^1.2.1",
+        "gopd": "^1.0.1"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/gopd": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
+      "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==",
+      "license": "MIT",
+      "optional": true,
+      "dependencies": {
+        "get-intrinsic": "^1.1.3"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/got": {
+      "version": "11.8.6",
+      "resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz",
+      "integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==",
+      "license": "MIT",
+      "dependencies": {
+        "@sindresorhus/is": "^4.0.0",
+        "@szmarczak/http-timer": "^4.0.5",
+        "@types/cacheable-request": "^6.0.1",
+        "@types/responselike": "^1.0.0",
+        "cacheable-lookup": "^5.0.3",
+        "cacheable-request": "^7.0.2",
+        "decompress-response": "^6.0.0",
+        "http2-wrapper": "^1.0.0-beta.5.2",
+        "lowercase-keys": "^2.0.0",
+        "p-cancelable": "^2.0.0",
+        "responselike": "^2.0.0"
+      },
+      "engines": {
+        "node": ">=10.19.0"
+      },
+      "funding": {
+        "url": "https://github.com/sindresorhus/got?sponsor=1"
+      }
+    },
+    "node_modules/graceful-fs": {
+      "version": "4.2.11",
+      "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
+      "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
+      "license": "ISC"
+    },
+    "node_modules/has-flag": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
+      "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/has-property-descriptors": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
+      "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
+      "license": "MIT",
+      "optional": true,
+      "dependencies": {
+        "es-define-property": "^1.0.0"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/has-proto": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz",
+      "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==",
+      "license": "MIT",
+      "optional": true,
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/has-symbols": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
+      "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
+      "license": "MIT",
+      "optional": true,
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/hasown": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
+      "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+      "license": "MIT",
+      "optional": true,
+      "dependencies": {
+        "function-bind": "^1.1.2"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/http-cache-semantics": {
+      "version": "4.1.1",
+      "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz",
+      "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==",
+      "license": "BSD-2-Clause"
+    },
+    "node_modules/http2-wrapper": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz",
+      "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==",
+      "license": "MIT",
+      "dependencies": {
+        "quick-lru": "^5.1.1",
+        "resolve-alpn": "^1.0.0"
+      },
+      "engines": {
+        "node": ">=10.19.0"
+      }
+    },
+    "node_modules/iconv-lite": {
+      "version": "0.4.24",
+      "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
+      "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
+      "license": "MIT",
+      "dependencies": {
+        "safer-buffer": ">= 2.1.2 < 3"
+      },
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/ieee754": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
+      "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/feross"
+        },
+        {
+          "type": "patreon",
+          "url": "https://www.patreon.com/feross"
+        },
+        {
+          "type": "consulting",
+          "url": "https://feross.org/support"
+        }
+      ],
+      "license": "BSD-3-Clause"
+    },
+    "node_modules/inherits": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+      "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+      "license": "ISC"
+    },
+    "node_modules/ini": {
+      "version": "1.3.8",
+      "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
+      "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
+      "license": "ISC"
+    },
+    "node_modules/inquirer": {
+      "version": "6.5.2",
+      "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.5.2.tgz",
+      "integrity": "sha512-cntlB5ghuB0iuO65Ovoi8ogLHiWGs/5yNrtUcKjFhSSiVeAIVpD7koaSU9RM8mpXw5YDi9RdYXGQMaOURB7ycQ==",
+      "license": "MIT",
+      "dependencies": {
+        "ansi-escapes": "^3.2.0",
+        "chalk": "^2.4.2",
+        "cli-cursor": "^2.1.0",
+        "cli-width": "^2.0.0",
+        "external-editor": "^3.0.3",
+        "figures": "^2.0.0",
+        "lodash": "^4.17.12",
+        "mute-stream": "0.0.7",
+        "run-async": "^2.2.0",
+        "rxjs": "^6.4.0",
+        "string-width": "^2.1.0",
+        "strip-ansi": "^5.1.0",
+        "through": "^2.3.6"
+      },
+      "engines": {
+        "node": ">=6.0.0"
+      }
+    },
+    "node_modules/is-fullwidth-code-point": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
+      "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/isarray": {
+      "version": "0.0.1",
+      "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
+      "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==",
+      "license": "MIT"
+    },
+    "node_modules/js-yaml": {
+      "version": "3.14.1",
+      "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
+      "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
+      "license": "MIT",
+      "dependencies": {
+        "argparse": "^1.0.7",
+        "esprima": "^4.0.0"
+      },
+      "bin": {
+        "js-yaml": "bin/js-yaml.js"
+      }
+    },
+    "node_modules/json-buffer": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
+      "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
+      "license": "MIT"
+    },
+    "node_modules/json-stringify-safe": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
+      "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==",
+      "license": "ISC",
+      "optional": true
+    },
+    "node_modules/jsonfile": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
+      "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==",
+      "license": "MIT",
+      "optionalDependencies": {
+        "graceful-fs": "^4.1.6"
+      }
+    },
+    "node_modules/keyv": {
+      "version": "4.5.4",
+      "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
+      "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
+      "license": "MIT",
+      "dependencies": {
+        "json-buffer": "3.0.1"
+      }
+    },
+    "node_modules/lodash": {
+      "version": "4.17.21",
+      "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
+      "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
+      "license": "MIT"
+    },
+    "node_modules/lowercase-keys": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz",
+      "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/matcher": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz",
+      "integrity": "sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==",
+      "license": "MIT",
+      "optional": true,
+      "dependencies": {
+        "escape-string-regexp": "^4.0.0"
+      },
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/matcher/node_modules/escape-string-regexp": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+      "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+      "license": "MIT",
+      "optional": true,
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/mime-db": {
+      "version": "1.52.0",
+      "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+      "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/mime-types": {
+      "version": "2.1.35",
+      "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+      "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+      "license": "MIT",
+      "dependencies": {
+        "mime-db": "1.52.0"
+      },
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/mimic-fn": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz",
+      "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/mimic-response": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz",
+      "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/minimist": {
+      "version": "1.2.8",
+      "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
+      "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
+      "license": "MIT",
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/mkdirp-classic": {
+      "version": "0.5.3",
+      "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz",
+      "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==",
+      "license": "MIT"
+    },
+    "node_modules/ms": {
+      "version": "2.1.3",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+      "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+      "license": "MIT"
+    },
+    "node_modules/mute-stream": {
+      "version": "0.0.7",
+      "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz",
+      "integrity": "sha512-r65nCZhrbXXb6dXOACihYApHw2Q6pV0M3V0PSxd74N0+D8nzAdEAITq2oAjA1jVnKI+tGvEBUpqiMh0+rW6zDQ==",
+      "license": "ISC"
+    },
+    "node_modules/nan": {
+      "version": "2.20.0",
+      "resolved": "https://registry.npmjs.org/nan/-/nan-2.20.0.tgz",
+      "integrity": "sha512-bk3gXBZDGILuuo/6sKtr0DQmSThYHLtNCdSdXk9YkxD/jK6X2vmCyyXBBxyqZ4XcnzTyYEAThfX3DCEnLf6igw==",
+      "license": "MIT"
+    },
+    "node_modules/napi-build-utils": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz",
+      "integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==",
+      "license": "MIT"
+    },
+    "node_modules/node-abi": {
+      "version": "3.68.0",
+      "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.68.0.tgz",
+      "integrity": "sha512-7vbj10trelExNjFSBm5kTvZXXa7pZyKWx9RCKIyqe6I9Ev3IzGpQoqBP3a+cOdxY+pWj6VkP28n/2wWysBHD/A==",
+      "license": "MIT",
+      "dependencies": {
+        "semver": "^7.3.5"
+      },
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/node-abi/node_modules/semver": {
+      "version": "7.6.3",
+      "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
+      "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
+      "license": "ISC",
+      "bin": {
+        "semver": "bin/semver.js"
+      },
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/normalize-url": {
+      "version": "6.1.0",
+      "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz",
+      "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/object-keys": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
+      "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
+      "license": "MIT",
+      "optional": true,
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/once": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+      "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
+      "license": "ISC",
+      "dependencies": {
+        "wrappy": "1"
+      }
+    },
+    "node_modules/onetime": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz",
+      "integrity": "sha512-oyyPpiMaKARvvcgip+JV+7zci5L8D1W9RZIz2l1o08AM3pfspitVWnPt3mzHcBPp12oYMTy0pqrFs/C+m3EwsQ==",
+      "license": "MIT",
+      "dependencies": {
+        "mimic-fn": "^1.0.0"
+      },
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/os-tmpdir": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
+      "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/p-cancelable": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz",
+      "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/pend": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz",
+      "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==",
+      "license": "MIT"
+    },
+    "node_modules/performance-now": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
+      "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==",
+      "license": "MIT"
+    },
+    "node_modules/pngjs": {
+      "version": "3.4.0",
+      "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-3.4.0.tgz",
+      "integrity": "sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=4.0.0"
+      }
+    },
+    "node_modules/prebuild-install": {
+      "version": "7.1.2",
+      "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.2.tgz",
+      "integrity": "sha512-UnNke3IQb6sgarcZIDU3gbMeTp/9SSU1DAIkil7PrqG1vZlBtY5msYccSKSHDqa3hNg436IXK+SNImReuA1wEQ==",
+      "license": "MIT",
+      "dependencies": {
+        "detect-libc": "^2.0.0",
+        "expand-template": "^2.0.3",
+        "github-from-package": "0.0.0",
+        "minimist": "^1.2.3",
+        "mkdirp-classic": "^0.5.3",
+        "napi-build-utils": "^1.0.1",
+        "node-abi": "^3.3.0",
+        "pump": "^3.0.0",
+        "rc": "^1.2.7",
+        "simple-get": "^4.0.0",
+        "tar-fs": "^2.0.0",
+        "tunnel-agent": "^0.6.0"
+      },
+      "bin": {
+        "prebuild-install": "bin.js"
+      },
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/progress": {
+      "version": "2.0.3",
+      "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
+      "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.4.0"
+      }
+    },
+    "node_modules/proxy-from-env": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
+      "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
+      "license": "MIT"
+    },
+    "node_modules/pump": {
+      "version": "3.0.2",
+      "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz",
+      "integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==",
+      "license": "MIT",
+      "dependencies": {
+        "end-of-stream": "^1.1.0",
+        "once": "^1.3.1"
+      }
+    },
+    "node_modules/quick-lru": {
+      "version": "5.1.1",
+      "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz",
+      "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/rc": {
+      "version": "1.2.8",
+      "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
+      "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
+      "license": "(BSD-2-Clause OR MIT OR Apache-2.0)",
+      "dependencies": {
+        "deep-extend": "^0.6.0",
+        "ini": "~1.3.0",
+        "minimist": "^1.2.0",
+        "strip-json-comments": "~2.0.1"
+      },
+      "bin": {
+        "rc": "cli.js"
+      }
+    },
+    "node_modules/readable-stream": {
+      "version": "1.1.14",
+      "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz",
+      "integrity": "sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==",
+      "license": "MIT",
+      "dependencies": {
+        "core-util-is": "~1.0.0",
+        "inherits": "~2.0.1",
+        "isarray": "0.0.1",
+        "string_decoder": "~0.10.x"
+      }
+    },
+    "node_modules/require-dir": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/require-dir/-/require-dir-1.2.0.tgz",
+      "integrity": "sha512-LY85DTSu+heYgDqq/mK+7zFHWkttVNRXC9NKcKGyuGLdlsfbjEPrIEYdCVrx6hqnJb+xSu3Lzaoo8VnmOhhjNA==",
+      "license": "MIT",
+      "engines": {
+        "node": "*"
+      }
+    },
+    "node_modules/require-directory": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
+      "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/resolve-alpn": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz",
+      "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==",
+      "license": "MIT"
+    },
+    "node_modules/responselike": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz",
+      "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==",
+      "license": "MIT",
+      "dependencies": {
+        "lowercase-keys": "^2.0.0"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/restore-cursor": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz",
+      "integrity": "sha512-6IzJLuGi4+R14vwagDHX+JrXmPVtPpn4mffDJ1UdR7/Edm87fl6yi8mMBIVvFtJaNTUvjughmW4hwLhRG7gC1Q==",
+      "license": "MIT",
+      "dependencies": {
+        "onetime": "^2.0.0",
+        "signal-exit": "^3.0.2"
+      },
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/roarr": {
+      "version": "2.15.4",
+      "resolved": "https://registry.npmjs.org/roarr/-/roarr-2.15.4.tgz",
+      "integrity": "sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==",
+      "license": "BSD-3-Clause",
+      "optional": true,
+      "dependencies": {
+        "boolean": "^3.0.1",
+        "detect-node": "^2.0.4",
+        "globalthis": "^1.0.1",
+        "json-stringify-safe": "^5.0.1",
+        "semver-compare": "^1.0.0",
+        "sprintf-js": "^1.1.2"
+      },
+      "engines": {
+        "node": ">=8.0"
+      }
+    },
+    "node_modules/roarr/node_modules/sprintf-js": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz",
+      "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==",
+      "license": "BSD-3-Clause",
+      "optional": true
+    },
+    "node_modules/run-async": {
+      "version": "2.4.1",
+      "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz",
+      "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.12.0"
+      }
+    },
+    "node_modules/rxjs": {
+      "version": "6.6.7",
+      "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz",
+      "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "tslib": "^1.9.0"
+      },
+      "engines": {
+        "npm": ">=2.0.0"
+      }
+    },
+    "node_modules/safe-buffer": {
+      "version": "5.2.1",
+      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+      "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/feross"
+        },
+        {
+          "type": "patreon",
+          "url": "https://www.patreon.com/feross"
+        },
+        {
+          "type": "consulting",
+          "url": "https://feross.org/support"
+        }
+      ],
+      "license": "MIT"
+    },
+    "node_modules/safer-buffer": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+      "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
+      "license": "MIT"
+    },
+    "node_modules/semver": {
+      "version": "6.3.1",
+      "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+      "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+      "license": "ISC",
+      "bin": {
+        "semver": "bin/semver.js"
+      }
+    },
+    "node_modules/semver-compare": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz",
+      "integrity": "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==",
+      "license": "MIT",
+      "optional": true
+    },
+    "node_modules/serialize-error": {
+      "version": "7.0.1",
+      "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-7.0.1.tgz",
+      "integrity": "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==",
+      "license": "MIT",
+      "optional": true,
+      "dependencies": {
+        "type-fest": "^0.13.1"
+      },
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/signal-exit": {
+      "version": "3.0.7",
+      "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
+      "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
+      "license": "ISC"
+    },
+    "node_modules/simple-concat": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz",
+      "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==",
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/feross"
+        },
+        {
+          "type": "patreon",
+          "url": "https://www.patreon.com/feross"
+        },
+        {
+          "type": "consulting",
+          "url": "https://feross.org/support"
+        }
+      ],
+      "license": "MIT"
+    },
+    "node_modules/simple-get": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz",
+      "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==",
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/feross"
+        },
+        {
+          "type": "patreon",
+          "url": "https://www.patreon.com/feross"
+        },
+        {
+          "type": "consulting",
+          "url": "https://feross.org/support"
+        }
+      ],
+      "license": "MIT",
+      "dependencies": {
+        "decompress-response": "^6.0.0",
+        "once": "^1.3.1",
+        "simple-concat": "^1.0.0"
+      }
+    },
+    "node_modules/sprintf-js": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
+      "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==",
+      "license": "BSD-3-Clause"
+    },
+    "node_modules/string_decoder": {
+      "version": "0.10.31",
+      "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
+      "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==",
+      "license": "MIT"
+    },
+    "node_modules/string-argv": {
+      "version": "0.0.2",
+      "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.0.2.tgz",
+      "integrity": "sha512-p6/Mqq0utTQWUeGMi/m0uBtlLZEwXSY3+mXzeRRqw7fz5ezUb28Wr0R99NlfbWaMmL/jCyT9be4jpn7Yz8IO8w==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.6.19"
+      }
+    },
+    "node_modules/string-width": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
+      "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==",
+      "license": "MIT",
+      "dependencies": {
+        "is-fullwidth-code-point": "^2.0.0",
+        "strip-ansi": "^4.0.0"
+      },
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/string-width/node_modules/ansi-regex": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz",
+      "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/string-width/node_modules/strip-ansi": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
+      "integrity": "sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow==",
+      "license": "MIT",
+      "dependencies": {
+        "ansi-regex": "^3.0.0"
+      },
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/strip-ansi": {
+      "version": "5.2.0",
+      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
+      "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
+      "license": "MIT",
+      "dependencies": {
+        "ansi-regex": "^4.1.0"
+      },
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/strip-json-comments": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
+      "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/sumchecker": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/sumchecker/-/sumchecker-3.0.1.tgz",
+      "integrity": "sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "debug": "^4.1.0"
+      },
+      "engines": {
+        "node": ">= 8.0"
+      }
+    },
+    "node_modules/supports-color": {
+      "version": "5.5.0",
+      "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+      "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+      "license": "MIT",
+      "dependencies": {
+        "has-flag": "^3.0.0"
+      },
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/tar-fs": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz",
+      "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==",
+      "license": "MIT",
+      "dependencies": {
+        "chownr": "^1.1.1",
+        "mkdirp-classic": "^0.5.2",
+        "pump": "^3.0.0",
+        "tar-stream": "^2.1.4"
+      }
+    },
+    "node_modules/tar-stream": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz",
+      "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==",
+      "license": "MIT",
+      "dependencies": {
+        "bl": "^4.0.3",
+        "end-of-stream": "^1.4.1",
+        "fs-constants": "^1.0.0",
+        "inherits": "^2.0.3",
+        "readable-stream": "^3.1.1"
+      },
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/tar-stream/node_modules/readable-stream": {
+      "version": "3.6.2",
+      "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
+      "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
+      "license": "MIT",
+      "dependencies": {
+        "inherits": "^2.0.3",
+        "string_decoder": "^1.1.1",
+        "util-deprecate": "^1.0.1"
+      },
+      "engines": {
+        "node": ">= 6"
+      }
+    },
+    "node_modules/tar-stream/node_modules/string_decoder": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
+      "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
+      "license": "MIT",
+      "dependencies": {
+        "safe-buffer": "~5.2.0"
+      }
+    },
+    "node_modules/terminalizer": {
+      "version": "0.12.0",
+      "resolved": "https://registry.npmjs.org/terminalizer/-/terminalizer-0.12.0.tgz",
+      "integrity": "sha512-eDi+ezFetag//7/0Y6C/XlxVPBjMkM3pvYUr1gV6CJy8hEx2vjNWqubiw87psdrMnXSOTDthDWSvpArECuM9cA==",
+      "license": "MIT",
+      "dependencies": {
+        "@homebridge/node-pty-prebuilt-multiarch": "^0.11.14",
+        "async": "^2.6.3",
+        "async-promises": "^0.2.2",
+        "axios": "^1.7.5",
+        "chalk": "^2.4.2",
+        "death": "^1.1.0",
+        "deepmerge": "^2.2.1",
+        "electron": "^25.2.0",
+        "fs-extra": "^5.0.0",
+        "gif-encoder": "^0.6.1",
+        "inquirer": "^6.5.2",
+        "js-yaml": "^3.13.1",
+        "lodash": "^4.17.15",
+        "performance-now": "^2.1.0",
+        "pngjs": "^3.4.0",
+        "progress": "^2.0.3",
+        "require-dir": "^1.1.0",
+        "string-argv": "0.0.2",
+        "tmp": "^0.2.1",
+        "uuid": "^10.0.0",
+        "yargs": "^17.7.2"
+      },
+      "bin": {
+        "terminalizer": "bin/app.js"
+      }
+    },
+    "node_modules/through": {
+      "version": "2.3.8",
+      "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
+      "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==",
+      "license": "MIT"
+    },
+    "node_modules/tmp": {
+      "version": "0.2.3",
+      "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz",
+      "integrity": "sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=14.14"
+      }
+    },
+    "node_modules/tslib": {
+      "version": "1.14.1",
+      "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
+      "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
+      "license": "0BSD"
+    },
+    "node_modules/tunnel-agent": {
+      "version": "0.6.0",
+      "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
+      "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "safe-buffer": "^5.0.1"
+      },
+      "engines": {
+        "node": "*"
+      }
+    },
+    "node_modules/type-fest": {
+      "version": "0.13.1",
+      "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz",
+      "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==",
+      "license": "(MIT OR CC0-1.0)",
+      "optional": true,
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/undici-types": {
+      "version": "5.26.5",
+      "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
+      "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
+      "license": "MIT"
+    },
+    "node_modules/universalify": {
+      "version": "0.1.2",
+      "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
+      "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 4.0.0"
+      }
+    },
+    "node_modules/util-deprecate": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+      "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
+      "license": "MIT"
+    },
+    "node_modules/uuid": {
+      "version": "10.0.0",
+      "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz",
+      "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==",
+      "funding": [
+        "https://github.com/sponsors/broofa",
+        "https://github.com/sponsors/ctavan"
+      ],
+      "license": "MIT",
+      "bin": {
+        "uuid": "dist/bin/uuid"
+      }
+    },
+    "node_modules/wrap-ansi": {
+      "version": "7.0.0",
+      "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+      "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+      "license": "MIT",
+      "dependencies": {
+        "ansi-styles": "^4.0.0",
+        "string-width": "^4.1.0",
+        "strip-ansi": "^6.0.0"
+      },
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+      }
+    },
+    "node_modules/wrap-ansi/node_modules/ansi-regex": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+      "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/wrap-ansi/node_modules/ansi-styles": {
+      "version": "4.3.0",
+      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+      "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+      "license": "MIT",
+      "dependencies": {
+        "color-convert": "^2.0.1"
+      },
+      "engines": {
+        "node": ">=8"
+      },
+      "funding": {
+        "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+      }
+    },
+    "node_modules/wrap-ansi/node_modules/color-convert": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+      "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+      "license": "MIT",
+      "dependencies": {
+        "color-name": "~1.1.4"
+      },
+      "engines": {
+        "node": ">=7.0.0"
+      }
+    },
+    "node_modules/wrap-ansi/node_modules/color-name": {
+      "version": "1.1.4",
+      "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+      "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+      "license": "MIT"
+    },
+    "node_modules/wrap-ansi/node_modules/is-fullwidth-code-point": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+      "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/wrap-ansi/node_modules/string-width": {
+      "version": "4.2.3",
+      "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+      "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+      "license": "MIT",
+      "dependencies": {
+        "emoji-regex": "^8.0.0",
+        "is-fullwidth-code-point": "^3.0.0",
+        "strip-ansi": "^6.0.1"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/wrap-ansi/node_modules/strip-ansi": {
+      "version": "6.0.1",
+      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+      "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+      "license": "MIT",
+      "dependencies": {
+        "ansi-regex": "^5.0.1"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/wrappy": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+      "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
+      "license": "ISC"
+    },
+    "node_modules/y18n": {
+      "version": "5.0.8",
+      "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
+      "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
+      "license": "ISC",
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/yargs": {
+      "version": "17.7.2",
+      "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
+      "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
+      "license": "MIT",
+      "dependencies": {
+        "cliui": "^8.0.1",
+        "escalade": "^3.1.1",
+        "get-caller-file": "^2.0.5",
+        "require-directory": "^2.1.1",
+        "string-width": "^4.2.3",
+        "y18n": "^5.0.5",
+        "yargs-parser": "^21.1.1"
+      },
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/yargs-parser": {
+      "version": "21.1.1",
+      "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
+      "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
+      "license": "ISC",
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/yargs/node_modules/ansi-regex": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+      "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/yargs/node_modules/is-fullwidth-code-point": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+      "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/yargs/node_modules/string-width": {
+      "version": "4.2.3",
+      "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+      "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+      "license": "MIT",
+      "dependencies": {
+        "emoji-regex": "^8.0.0",
+        "is-fullwidth-code-point": "^3.0.0",
+        "strip-ansi": "^6.0.1"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/yargs/node_modules/strip-ansi": {
+      "version": "6.0.1",
+      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+      "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+      "license": "MIT",
+      "dependencies": {
+        "ansi-regex": "^5.0.1"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/yauzl": {
+      "version": "2.10.0",
+      "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz",
+      "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==",
+      "license": "MIT",
+      "dependencies": {
+        "buffer-crc32": "~0.2.3",
+        "fd-slicer": "~1.1.0"
+      }
+    }
+  }
+}

+ 5 - 0
package.json

@@ -0,0 +1,5 @@
+{
+  "dependencies": {
+    "terminalizer": "^0.12.0"
+  }
+}

+ 1 - 1
pyproject.toml

@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
 
 
 [project]
 [project]
 name = "flipperzero"
 name = "flipperzero"
-version = "1.3.0"
+version = "1.5.0"
 authors = [
 authors = [
     { name = "Oliver Fabel" },
     { name = "Oliver Fabel" },
 ]
 ]

+ 40 - 143
upython.c

@@ -1,171 +1,68 @@
-#include <malloc.h>
-
 #include <furi.h>
 #include <furi.h>
-#include <gui/gui.h>
-#include <dialogs/dialogs.h>
 #include <storage/storage.h>
 #include <storage/storage.h>
 
 
 #include <mp_flipper_runtime.h>
 #include <mp_flipper_runtime.h>
 #include <mp_flipper_compiler.h>
 #include <mp_flipper_compiler.h>
 
 
-#include "upython_icons.h"
-
-#define TAG "uPython"
-
-typedef enum {
-    ActionNone,
-    ActionOpen,
-    ActionExit
-} Action;
-
-static Action action = ActionNone;
-
-static void execute_file(FuriString* file) {
-    size_t stack;
-
-    const char* path = furi_string_get_cstr(file);
-    FuriString* file_path = furi_string_alloc_printf("%s", path);
-
-    do {
-        FURI_LOG_I(TAG, "executing script %s", path);
-
-        const size_t heap_size = memmgr_get_free_heap() * 0.1;
-        const size_t stack_size = 2 * 1024;
-        uint8_t* heap = malloc(heap_size * sizeof(uint8_t));
-
-        FURI_LOG_D(TAG, "initial heap size is %zu bytes", heap_size);
-        FURI_LOG_D(TAG, "stack size is %zu bytes", stack_size);
-
-        size_t index = furi_string_search_rchar(file_path, '/');
-
-        furi_check(index != FURI_STRING_FAILURE);
+#include "upython.h"
 
 
-        bool is_py_file = furi_string_end_with_str(file_path, ".py");
+volatile Action action = ActionNone;
+FuriString* file_path = NULL;
+volatile FuriThreadStdoutWriteCallback stdout_callback = NULL;
 
 
-        furi_string_left(file_path, index);
-
-        mp_flipper_set_root_module_path(furi_string_get_cstr(file_path));
-
-        mp_flipper_init(heap, heap_size, stack_size, &stack);
-
-        if(is_py_file) {
-            mp_flipper_exec_py_file(path);
-        } else {
-            mp_flipper_exec_mpy_file(path);
-        }
-
-        mp_flipper_deinit();
-
-        free(heap);
-    } while(false);
-
-    furi_string_free(file_path);
+static void write_to_log_output(const char* data, size_t size) {
+    furi_log_tx((const uint8_t*)data, size);
 }
 }
 
 
-static bool select_python_file(FuriString* file_path) {
-    DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS);
-
-    DialogsFileBrowserOptions browser_options;
-
-    dialog_file_browser_set_basic_options(&browser_options, "py", NULL);
-
-    browser_options.hide_ext = false;
-    browser_options.base_path = STORAGE_APP_DATA_PATH_PREFIX;
-
-    bool result = dialog_file_browser_show(dialogs, file_path, file_path, &browser_options);
-
-    furi_record_close(RECORD_DIALOGS);
-
-    return result;
-}
-
-static void on_input(const void* event, void* ctx) {
-    UNUSED(ctx);
-
-    InputKey key = ((InputEvent*)event)->key;
-    InputType type = ((InputEvent*)event)->type;
-
-    if(type != InputTypeRelease) {
-        return;
-    }
-
-    switch(key) {
-    case InputKeyOk:
-        action = ActionOpen;
-        break;
-    case InputKeyBack:
-        action = ActionExit;
-        break;
-    default:
-        action = ActionNone;
-        break;
-    }
+void upython_reset_file_path() {
+    furi_string_set(file_path, APP_ASSETS_PATH("upython"));
 }
 }
 
 
-static void show_splash_screen() {
-    Gui* gui = furi_record_open(RECORD_GUI);
-    FuriPubSub* input_event_queue = furi_record_open(RECORD_INPUT_EVENTS);
-    FuriPubSubSubscription* input_event = furi_pubsub_subscribe(input_event_queue, on_input, NULL);
-
-    ViewPort* view_port = view_port_alloc();
-
-    gui_add_view_port(gui, view_port, GuiLayerFullscreen);
-
-    Canvas* canvas = gui_direct_draw_acquire(gui);
+int32_t upython(void* args) {
+    upython_cli_register(args);
 
 
-    canvas_draw_icon(canvas, 0, 0, &I_splash);
-    canvas_draw_icon(canvas, 82, 17, &I_qrcode);
-    canvas_set_color(canvas, ColorBlack);
-    canvas_set_font(canvas, FontSecondary);
-    canvas_draw_str_aligned(canvas, 66, 3, AlignLeft, AlignTop, "Micro");
-    canvas_set_font(canvas, FontPrimary);
-    canvas_draw_str_aligned(canvas, 90, 2, AlignLeft, AlignTop, "Python");
-
-    canvas_set_font(canvas, FontSecondary);
-
-    canvas_draw_icon(canvas, 65, 53, &I_Pin_back_arrow_10x8);
-    canvas_draw_str_aligned(canvas, 78, 54, AlignLeft, AlignTop, "Exit");
-
-    canvas_draw_icon(canvas, 98, 54, &I_ButtonCenter_7x7);
-    canvas_draw_str_aligned(canvas, 107, 54, AlignLeft, AlignTop, "Open");
-
-    canvas_commit(canvas);
+    do {
+        switch(action) {
+        case ActionNone:
+            action = upython_splash_screen();
 
 
-    while(action == ActionNone) {
-        furi_delay_ms(1);
-    }
+            break;
+        case ActionOpen:
+            if(upython_select_python_file(file_path)) {
+                stdout_callback = write_to_log_output;
+                action = ActionExec;
+            } else {
+                upython_reset_file_path();
 
 
-    furi_pubsub_unsubscribe(input_event_queue, input_event);
+                action = ActionNone;
+            }
 
 
-    gui_direct_draw_release(gui);
-    gui_remove_view_port(gui, view_port);
+            break;
+        case ActionRepl:
+            break;
+        case ActionExec:
+            furi_thread_set_stdout_callback(stdout_callback);
 
 
-    view_port_free(view_port);
+            upython_file_execute(file_path);
 
 
-    furi_record_close(RECORD_INPUT_EVENTS);
-    furi_record_close(RECORD_GUI);
-}
+            upython_reset_file_path();
 
 
-int32_t upython(void* p) {
-    UNUSED(p);
+            action = ActionNone;
 
 
-    do {
-        show_splash_screen();
+            furi_thread_set_stdout_callback(stdout_callback = NULL);
 
 
-        if(action == ActionExit) {
+            break;
+        case ActionExit:
+            action = upython_confirm_exit_action() ? ActionTerm : ActionNone;
+            break;
+        case ActionTerm:
             break;
             break;
         }
         }
 
 
-        FuriString* file_path = furi_string_alloc_set_str(APP_ASSETS_PATH("upython"));
-
-        if(select_python_file(file_path)) {
-            execute_file(file_path);
-        }
-
-        furi_string_free(file_path);
+        furi_delay_ms(1);
+    } while(action != ActionTerm);
 
 
-        action = ActionNone;
-    } while(true);
+    upython_cli_unregister(args);
 
 
     return 0;
     return 0;
 }
 }

+ 35 - 0
upython.h

@@ -0,0 +1,35 @@
+#pragma once
+
+#include <cli/cli.h>
+#include <furi.h>
+
+#define TAG "uPython"
+#define CLI "py"
+
+typedef enum {
+    ActionNone,
+    ActionOpen,
+    ActionRepl,
+    ActionExec,
+    ActionExit,
+    ActionTerm
+} Action;
+
+extern FuriString* file_path;
+extern volatile Action action;
+extern volatile FuriThreadStdoutWriteCallback stdout_callback;
+
+void upython_reset_file_path();
+
+Action upython_splash_screen();
+bool upython_confirm_exit_action();
+bool upython_select_python_file(FuriString* file_path);
+
+void upython_cli_register(void* args);
+void upython_cli_unregister(void* args);
+
+void upython_cli(Cli* cli, FuriString* args, void* ctx);
+
+void upython_repl_execute(Cli* cli);
+
+void upython_file_execute(FuriString* file);

+ 82 - 0
upython_cli.c

@@ -0,0 +1,82 @@
+#include <furi.h>
+#include <storage/storage.h>
+
+#include "upython.h"
+
+static FuriStreamBuffer* stdout_buffer = NULL;
+
+static void write_to_stdout_buffer(const char* data, size_t size) {
+    furi_stream_buffer_send(stdout_buffer, data, size, 0);
+}
+
+void upython_cli(Cli* cli, FuriString* args, void* ctx) {
+    UNUSED(ctx);
+
+    if(action != ActionNone) {
+        printf("%s is busy!\n", TAG);
+
+        return;
+    }
+
+    if(furi_string_empty(args)) {
+        action = ActionRepl;
+
+        upython_repl_execute(cli);
+
+        action = ActionNone;
+    } else {
+        furi_string_set(file_path, args);
+
+        stdout_buffer = furi_stream_buffer_alloc(128, 1);
+
+        stdout_callback = write_to_stdout_buffer;
+
+        action = ActionExec;
+
+        char data = '\0';
+
+        while(action == ActionExec || !furi_stream_buffer_is_empty(stdout_buffer)) {
+            if(furi_stream_buffer_receive(stdout_buffer, &data, 1, 0) > 0) {
+                printf("%c", data);
+            }
+        }
+
+        furi_stream_buffer_free(stdout_buffer);
+    }
+}
+
+void upython_cli_register(void* args) {
+    if(args != NULL) {
+        file_path = furi_string_alloc_set_str(args);
+
+        action = ActionExec;
+
+        return;
+    } else {
+        file_path = furi_string_alloc();
+
+        upython_reset_file_path();
+
+        action = ActionNone;
+    }
+
+    Cli* cli = furi_record_open(RECORD_CLI);
+
+    cli_add_command(cli, CLI, CliCommandFlagParallelSafe, upython_cli, NULL);
+
+    furi_record_close(RECORD_CLI);
+}
+
+void upython_cli_unregister(void* args) {
+    furi_string_free(file_path);
+
+    if(args != NULL) {
+        return;
+    }
+
+    Cli* cli = furi_record_open(RECORD_CLI);
+
+    cli_delete_command(cli, CLI);
+
+    furi_record_close(RECORD_CLI);
+}

+ 53 - 0
upython_file.c

@@ -0,0 +1,53 @@
+#include <cli/cli.h>
+#include <furi.h>
+
+#include <genhdr/mpversion.h>
+#include <mp_flipper_compiler.h>
+
+#include <mp_flipper_repl.h>
+
+#include "upython.h"
+
+void upython_file_execute(FuriString* file) {
+    size_t stack;
+
+    const char* path = furi_string_get_cstr(file);
+    FuriString* file_path = furi_string_alloc_printf("%s", path);
+
+    do {
+        FURI_LOG_I(TAG, "executing script %s", path);
+
+        const size_t heap_size = memmgr_get_free_heap() * 0.1;
+        const size_t stack_size = 2 * 1024;
+        uint8_t* heap = malloc(heap_size * sizeof(uint8_t));
+
+        FURI_LOG_D(TAG, "initial heap size is %zu bytes", heap_size);
+        FURI_LOG_D(TAG, "stack size is %zu bytes", stack_size);
+
+        size_t index = furi_string_search_rchar(file_path, '/');
+
+        if(index == FURI_STRING_FAILURE) {
+            FURI_LOG_E(TAG, "invalid file path");
+
+            break;
+        }
+
+        bool is_py_file = furi_string_end_with_str(file_path, ".py");
+
+        furi_string_left(file_path, index);
+
+        mp_flipper_set_root_module_path(furi_string_get_cstr(file_path));
+
+        mp_flipper_init(heap, heap_size, stack_size, &stack);
+
+        if(is_py_file) {
+            mp_flipper_exec_py_file(path);
+        }
+
+        mp_flipper_deinit();
+
+        free(heap);
+    } while(false);
+
+    furi_string_free(file_path);
+}

+ 394 - 0
upython_repl.c

@@ -0,0 +1,394 @@
+#include <stdio.h>
+
+#include <cli/cli.h>
+#include <furi.h>
+
+#include <genhdr/mpversion.h>
+#include <mp_flipper_compiler.h>
+
+#include <mp_flipper_repl.h>
+
+#include "upython.h"
+
+#define AUTOCOMPLETE_MANY_MATCHES (size_t)(-1)
+#define HISTORY_SIZE              16
+
+typedef struct {
+    FuriString** stack;
+    size_t pointer;
+    size_t size;
+} mp_flipper_repl_history_t;
+
+typedef struct {
+    mp_flipper_repl_history_t* history;
+    FuriString* line;
+    FuriString* code;
+    size_t cursor;
+    bool is_ps2;
+} mp_flipper_repl_context_t;
+
+static mp_flipper_repl_history_t* mp_flipper_repl_history_alloc() {
+    mp_flipper_repl_history_t* history = malloc(sizeof(mp_flipper_repl_history_t));
+
+    history->stack = malloc(HISTORY_SIZE * sizeof(FuriString*));
+    history->pointer = 0;
+    history->size = 1;
+
+    for(size_t i = 0; i < HISTORY_SIZE; i++) {
+        history->stack[i] = furi_string_alloc();
+    }
+
+    return history;
+}
+
+static void mp_flipper_repl_history_free(mp_flipper_repl_history_t* history) {
+    for(size_t i = 0; i < HISTORY_SIZE; i++) {
+        furi_string_free(history->stack[i]);
+    }
+
+    free(history);
+}
+
+static mp_flipper_repl_context_t* mp_flipper_repl_context_alloc() {
+    mp_flipper_repl_context_t* context = malloc(sizeof(mp_flipper_repl_context_t));
+
+    context->history = mp_flipper_repl_history_alloc();
+    context->code = furi_string_alloc();
+    context->line = furi_string_alloc();
+    context->cursor = 0;
+    context->is_ps2 = false;
+
+    return context;
+}
+
+static void mp_flipper_repl_context_free(mp_flipper_repl_context_t* context) {
+    mp_flipper_repl_history_free(context->history);
+
+    furi_string_free(context->code);
+    furi_string_free(context->line);
+
+    free(context);
+}
+
+static void print_full_psx(mp_flipper_repl_context_t* context) {
+    const char* psx = context->is_ps2 ? "... " : ">>> ";
+
+    printf("\e[2K\r%s%s", psx, furi_string_get_cstr(context->line));
+
+    fflush(stdout);
+
+    for(size_t i = context->cursor; i < furi_string_size(context->line); i++) {
+        printf("\e[D");
+    }
+
+    fflush(stdout);
+}
+
+inline static void handle_arrow_keys(char character, mp_flipper_repl_context_t* context) {
+    mp_flipper_repl_history_t* history = context->history;
+
+    do {
+        bool update_by_history = false;
+        // up arrow
+        if(character == 'A' && history->pointer == 0) {
+            furi_string_set(history->stack[0], context->line);
+        }
+
+        if(character == 'A' && history->pointer < history->size) {
+            history->pointer += (history->pointer + 1) == history->size ? 0 : 1;
+
+            update_by_history = true;
+        }
+
+        // down arrow
+        if(character == 'B' && history->pointer > 0) {
+            history->pointer--;
+
+            update_by_history = true;
+        }
+
+        if(update_by_history) {
+            furi_string_set(context->line, history->stack[history->pointer]);
+
+            context->cursor = furi_string_size(context->line);
+
+            break;
+        }
+
+        // right arrow
+        if(character == 'C' && context->cursor != furi_string_size(context->line)) {
+            context->cursor++;
+
+            break;
+        }
+
+        // left arrow
+        if(character == 'D' && context->cursor > 0) {
+            context->cursor--;
+
+            break;
+        }
+    } while(false);
+
+    print_full_psx(context);
+}
+
+inline static void handle_backspace(mp_flipper_repl_context_t* context) {
+    // skip backspace at begin of line
+    if(context->cursor == 0) {
+        return;
+    }
+
+    const char* line = furi_string_get_cstr(context->line);
+    size_t before = context->cursor - 1;
+    size_t after = furi_string_size(context->line) - context->cursor;
+
+    furi_string_printf(context->line, "%.*s%.*s", before, line, after, line + context->cursor);
+
+    context->cursor--;
+
+    printf("\e[D\e[1P");
+
+    fflush(stdout);
+}
+
+inline static bool is_indent_required(mp_flipper_repl_context_t* context) {
+    for(size_t i = 0; context->is_ps2 && i < context->cursor; i++) {
+        if(furi_string_get_char(context->line, i) != ' ') {
+            return false;
+        }
+    }
+
+    return context->is_ps2;
+}
+
+inline static void handle_autocomplete(mp_flipper_repl_context_t* context) {
+    // check if ps2 is active and just a tab character is required
+    if(is_indent_required(context)) {
+        furi_string_replace_at(context->line, context->cursor, 0, "    ");
+        context->cursor += 4;
+
+        print_full_psx(context);
+
+        return;
+    }
+
+    const char* new_line = furi_string_get_cstr(context->line);
+    FuriString* orig_line = furi_string_alloc_printf("%s", new_line);
+    const char* orig_line_str = furi_string_get_cstr(orig_line);
+
+    char* completion = malloc(128 * sizeof(char));
+
+    mp_print_t* print = malloc(sizeof(mp_print_t));
+
+    print->data = mp_flipper_print_data_alloc();
+    print->print_strn = mp_flipper_print_strn;
+
+    size_t length = mp_flipper_repl_autocomplete(new_line, context->cursor, print, &completion);
+
+    do {
+        if(length == 0) {
+            break;
+        }
+
+        if(length != AUTOCOMPLETE_MANY_MATCHES) {
+            furi_string_printf(
+                context->line,
+                "%.*s%.*s%s",
+                context->cursor,
+                orig_line_str,
+                length,
+                completion,
+                orig_line_str + context->cursor);
+
+            context->cursor += length;
+        } else {
+            printf("%s", mp_flipper_print_get_data(print->data));
+        }
+
+        print_full_psx(context);
+    } while(false);
+
+    mp_flipper_print_data_free(print->data);
+    furi_string_free(orig_line);
+    free(completion);
+    free(print);
+}
+
+inline static void update_history(mp_flipper_repl_context_t* context) {
+    mp_flipper_repl_history_t* history = context->history;
+
+    if(!furi_string_empty(context->line) && !furi_string_equal(context->line, history->stack[1])) {
+        history->size += history->size == HISTORY_SIZE ? 0 : 1;
+
+        for(size_t i = history->size - 1; i > 1; i--) {
+            furi_string_set(history->stack[i], history->stack[i - 1]);
+        }
+
+        furi_string_set(history->stack[1], context->line);
+    }
+
+    furi_string_reset(history->stack[0]);
+
+    history->pointer = 0;
+}
+
+inline static bool continue_with_input(mp_flipper_repl_context_t* context) {
+    if(furi_string_empty(context->line)) {
+        return false;
+    }
+
+    if(!mp_flipper_repl_continue_with_input(furi_string_get_cstr(context->code))) {
+        return false;
+    }
+
+    return true;
+}
+
+void upython_repl_execute(Cli* cli) {
+    size_t stack;
+
+    const size_t heap_size = memmgr_get_free_heap() * 0.1;
+    const size_t stack_size = 2 * 1024;
+    uint8_t* heap = malloc(heap_size * sizeof(uint8_t));
+
+    printf("MicroPython (%s, %s) on Flipper Zero\r\n", MICROPY_GIT_TAG, MICROPY_BUILD_DATE);
+    printf("Quit: Ctrl+D | Heap: %zu bytes | Stack: %zu bytes\r\n", heap_size, stack_size);
+    printf("      To do a reboot, press Left+Back for 5 seconds.\r\n");
+    printf("Docs: https://ofabel.github.io/mp-flipper\r\n");
+
+    mp_flipper_repl_context_t* context = mp_flipper_repl_context_alloc();
+
+    mp_flipper_set_root_module_path("/ext");
+    mp_flipper_init(heap, heap_size, stack_size, &stack);
+
+    char character = '\0';
+
+    uint8_t* buffer = malloc(sizeof(uint8_t));
+
+    bool exit = false;
+
+    // REPL loop
+    do {
+        furi_string_reset(context->code);
+
+        context->is_ps2 = false;
+
+        // scan line loop
+        do {
+            furi_string_reset(context->line);
+
+            context->cursor = 0;
+
+            print_full_psx(context);
+
+            // scan character loop
+            do {
+                character = cli_getc(cli);
+
+                // Ctrl + C
+                if(character == CliSymbolAsciiETX) {
+                    context->cursor = 0;
+
+                    furi_string_reset(context->line);
+                    furi_string_reset(context->code);
+
+                    printf("\r\nKeyboardInterrupt\r\n");
+
+                    break;
+                }
+
+                // Ctrl + D
+                if(character == CliSymbolAsciiEOT) {
+                    exit = true;
+
+                    break;
+                }
+
+                // skip line feed
+                if(character == CliSymbolAsciiLF) {
+                    continue;
+                }
+
+                // handle carriage return
+                if(character == CliSymbolAsciiCR) {
+                    furi_string_push_back(context->code, '\n');
+                    furi_string_cat(context->code, context->line);
+                    furi_string_trim(context->code);
+
+                    cli_nl(cli);
+
+                    break;
+                }
+
+                // handle arrow keys
+                if(character >= 0x18 && character <= 0x1B) {
+                    character = cli_getc(cli);
+                    character = cli_getc(cli);
+
+                    handle_arrow_keys(character, context);
+
+                    continue;
+                }
+
+                // handle tab, do autocompletion
+                if(character == CliSymbolAsciiTab) {
+                    handle_autocomplete(context);
+
+                    continue;
+                }
+
+                // handle backspace
+                if(character == CliSymbolAsciiBackspace || character == CliSymbolAsciiDel) {
+                    handle_backspace(context);
+
+                    continue;
+                }
+
+                // append at end
+                if(context->cursor == furi_string_size(context->line)) {
+                    buffer[0] = character;
+                    cli_write(cli, (const uint8_t*)buffer, 1);
+
+                    furi_string_push_back(context->line, character);
+
+                    context->cursor++;
+
+                    continue;
+                }
+
+                // insert between
+                if(context->cursor < furi_string_size(context->line)) {
+                    const char temp[2] = {character, 0};
+                    furi_string_replace_at(context->line, context->cursor++, 0, temp);
+
+                    printf("\e[4h%c\e[4l", character);
+                    fflush(stdout);
+
+                    continue;
+                }
+            } while(true);
+
+            // Ctrl + D
+            if(exit) {
+                break;
+            }
+
+            update_history(context);
+        } while((context->is_ps2 = continue_with_input(context)));
+
+        // Ctrl + D
+        if(exit) {
+            break;
+        }
+
+        mp_flipper_exec_str(furi_string_get_cstr(context->code));
+    } while(true);
+
+    mp_flipper_deinit();
+
+    mp_flipper_repl_context_free(context);
+
+    free(heap);
+    free(buffer);
+}

+ 114 - 0
upython_splash.c

@@ -0,0 +1,114 @@
+#include <malloc.h>
+
+#include <furi.h>
+#include <gui/gui.h>
+#include <dialogs/dialogs.h>
+#include <storage/storage.h>
+#include <cli/cli.h>
+#include <cli/cli_vcp.h>
+
+#include <mp_flipper_runtime.h>
+#include <mp_flipper_compiler.h>
+
+#include "upython.h"
+#include "upython_icons.h"
+
+static void on_input(const void* event, void* ctx) {
+    UNUSED(ctx);
+
+    InputKey key = ((InputEvent*)event)->key;
+    InputType type = ((InputEvent*)event)->type;
+
+    if(type != InputTypeRelease) {
+        return;
+    }
+
+    switch(key) {
+    case InputKeyOk:
+        action = ActionOpen;
+        break;
+    case InputKeyBack:
+        action = ActionExit;
+        break;
+    default:
+        action = ActionNone;
+        break;
+    }
+}
+
+bool upython_confirm_exit_action() {
+    DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS);
+
+    DialogMessage* message = dialog_message_alloc();
+
+    dialog_message_set_text(message, "Close uPython?", 64, 32, AlignCenter, AlignCenter);
+    dialog_message_set_buttons(message, "Yes", NULL, "No");
+
+    DialogMessageButton button = dialog_message_show(dialogs, message);
+
+    dialog_message_free(message);
+
+    furi_record_close(RECORD_DIALOGS);
+
+    return button == DialogMessageButtonLeft;
+}
+
+bool upython_select_python_file(FuriString* file_path) {
+    DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS);
+
+    DialogsFileBrowserOptions browser_options;
+
+    dialog_file_browser_set_basic_options(&browser_options, "py", NULL);
+
+    browser_options.hide_ext = false;
+    browser_options.base_path = STORAGE_APP_DATA_PATH_PREFIX;
+
+    bool result = dialog_file_browser_show(dialogs, file_path, file_path, &browser_options);
+
+    furi_record_close(RECORD_DIALOGS);
+
+    return result;
+}
+
+Action upython_splash_screen() {
+    if(action != ActionNone) {
+        return action;
+    }
+
+    Gui* gui = furi_record_open(RECORD_GUI);
+    FuriPubSub* input_event_queue = furi_record_open(RECORD_INPUT_EVENTS);
+    FuriPubSubSubscription* input_event = furi_pubsub_subscribe(input_event_queue, on_input, NULL);
+
+    Canvas* canvas = gui_direct_draw_acquire(gui);
+
+    canvas_draw_icon(canvas, 0, 0, &I_splash);
+    canvas_draw_icon(canvas, 82, 17, &I_qrcode);
+    canvas_set_color(canvas, ColorBlack);
+    canvas_set_font(canvas, FontSecondary);
+    canvas_draw_str_aligned(canvas, 66, 3, AlignLeft, AlignTop, "Micro");
+    canvas_set_font(canvas, FontPrimary);
+    canvas_draw_str_aligned(canvas, 90, 2, AlignLeft, AlignTop, "Python");
+
+    canvas_set_font(canvas, FontSecondary);
+
+    canvas_draw_icon(canvas, 65, 53, &I_Pin_back_arrow_10x8);
+    canvas_draw_str_aligned(canvas, 78, 54, AlignLeft, AlignTop, "Exit");
+
+    canvas_draw_icon(canvas, 98, 54, &I_ButtonCenter_7x7);
+    canvas_draw_str_aligned(canvas, 107, 54, AlignLeft, AlignTop, "Open");
+
+    canvas_commit(canvas);
+
+    while(action == ActionNone) {
+        furi_delay_ms(1);
+    }
+
+    furi_pubsub_unsubscribe(input_event_queue, input_event);
+
+    gui_direct_draw_release(gui);
+
+    furi_record_close(RECORD_INPUT_EVENTS);
+    furi_record_close(RECORD_GUI);
+
+    return action;
+}