mposcli 0.4.1__tar.gz → 0.6.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. {mposcli-0.4.1 → mposcli-0.6.0}/PKG-INFO +44 -11
  2. {mposcli-0.4.1 → mposcli-0.6.0}/README.md +43 -10
  3. {mposcli-0.4.1 → mposcli-0.6.0}/mposcli/__init__.py +1 -1
  4. {mposcli-0.4.1 → mposcli-0.6.0}/mposcli/cli_app/build.py +1 -1
  5. {mposcli-0.4.1 → mposcli-0.6.0}/mposcli/cli_app/copy_mpos.py +24 -44
  6. {mposcli-0.4.1 → mposcli-0.6.0}/mposcli/cli_app/flash.py +15 -7
  7. mposcli-0.6.0/mposcli/cli_app/shell.py +45 -0
  8. mposcli-0.6.0/mposcli/tests/test_mpremote_cp_utils.py +74 -0
  9. {mposcli-0.4.1 → mposcli-0.6.0}/mposcli/tests/test_readme.py +13 -1
  10. {mposcli-0.4.1 → mposcli-0.6.0}/mposcli/tools.py +3 -0
  11. {mposcli-0.4.1 → mposcli-0.6.0}/mposcli/user_input.py +5 -4
  12. mposcli-0.6.0/mposcli/utilities/__init__.py +0 -0
  13. mposcli-0.6.0/mposcli/utilities/mpremote.py +64 -0
  14. {mposcli-0.4.1 → mposcli-0.6.0}/.editorconfig +0 -0
  15. {mposcli-0.4.1 → mposcli-0.6.0}/.github/workflows/tests.yml +0 -0
  16. {mposcli-0.4.1 → mposcli-0.6.0}/.gitignore +0 -0
  17. {mposcli-0.4.1 → mposcli-0.6.0}/.idea/.gitignore +0 -0
  18. {mposcli-0.4.1 → mposcli-0.6.0}/.pre-commit-config.yaml +0 -0
  19. {mposcli-0.4.1 → mposcli-0.6.0}/.pre-commit-hooks.yaml +0 -0
  20. {mposcli-0.4.1 → mposcli-0.6.0}/.run/Template Python tests.run.xml +0 -0
  21. {mposcli-0.4.1 → mposcli-0.6.0}/.run/Template Python.run.xml +0 -0
  22. {mposcli-0.4.1 → mposcli-0.6.0}/.run/Unittests __all__.run.xml +0 -0
  23. {mposcli-0.4.1 → mposcli-0.6.0}/.run/cli --help.run.xml +0 -0
  24. {mposcli-0.4.1 → mposcli-0.6.0}/.run/dev-cli --help.run.xml +0 -0
  25. {mposcli-0.4.1 → mposcli-0.6.0}/.run/dev-cli test.run.xml +0 -0
  26. {mposcli-0.4.1 → mposcli-0.6.0}/.venv-app/.gitignore +0 -0
  27. {mposcli-0.4.1 → mposcli-0.6.0}/.venv-app/lib/python3.14/site-packages/cli_base/tests/shell_complete_snapshots/.gitignore +0 -0
  28. {mposcli-0.4.1 → mposcli-0.6.0}/cli.py +0 -0
  29. {mposcli-0.4.1 → mposcli-0.6.0}/dev-cli.py +0 -0
  30. {mposcli-0.4.1 → mposcli-0.6.0}/dist/.gitignore +0 -0
  31. {mposcli-0.4.1 → mposcli-0.6.0}/mposcli/__main__.py +0 -0
  32. {mposcli-0.4.1 → mposcli-0.6.0}/mposcli/cli_app/__init__.py +0 -0
  33. {mposcli-0.4.1 → mposcli-0.6.0}/mposcli/cli_app/run_deskop.py +0 -0
  34. {mposcli-0.4.1 → mposcli-0.6.0}/mposcli/cli_app/update.py +0 -0
  35. {mposcli-0.4.1 → mposcli-0.6.0}/mposcli/cli_dev/__init__.py +0 -0
  36. {mposcli-0.4.1 → mposcli-0.6.0}/mposcli/cli_dev/__main__.py +0 -0
  37. {mposcli-0.4.1 → mposcli-0.6.0}/mposcli/cli_dev/code_style.py +0 -0
  38. {mposcli-0.4.1 → mposcli-0.6.0}/mposcli/cli_dev/packaging.py +0 -0
  39. {mposcli-0.4.1 → mposcli-0.6.0}/mposcli/cli_dev/shell_completion.py +0 -0
  40. {mposcli-0.4.1 → mposcli-0.6.0}/mposcli/cli_dev/testing.py +0 -0
  41. {mposcli-0.4.1 → mposcli-0.6.0}/mposcli/cli_dev/update_readme_history.py +0 -0
  42. {mposcli-0.4.1 → mposcli-0.6.0}/mposcli/constants.py +0 -0
  43. {mposcli-0.4.1 → mposcli-0.6.0}/mposcli/fs_utils.py +0 -0
  44. {mposcli-0.4.1 → mposcli-0.6.0}/mposcli/mpos_utils.py +0 -0
  45. {mposcli-0.4.1 → mposcli-0.6.0}/mposcli/tests/__init__.py +0 -0
  46. {mposcli-0.4.1 → mposcli-0.6.0}/mposcli/tests/test_doctests.py +0 -0
  47. {mposcli-0.4.1 → mposcli-0.6.0}/mposcli/tests/test_project_setup.py +0 -0
  48. {mposcli-0.4.1 → mposcli-0.6.0}/mposcli/tests/test_readme_history.py +0 -0
  49. {mposcli-0.4.1 → mposcli-0.6.0}/noxfile.py +0 -0
  50. {mposcli-0.4.1 → mposcli-0.6.0}/pyproject.toml +0 -0
  51. {mposcli-0.4.1 → mposcli-0.6.0}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mposcli
3
- Version: 0.4.1
3
+ Version: 0.6.0
4
4
  Summary: CLI helper for MicroPythonOS: https://github.com/MicroPythonOS/MicroPythonOS
5
5
  Project-URL: Documentation, https://github.com/jedie/mposcli
6
6
  Project-URL: Source, https://github.com/jedie/mposcli
@@ -47,7 +47,8 @@ cd ~/MicroPythonOS
47
47
 
48
48
  [comment]: <> (✂✂✂ auto generated main help start ✂✂✂)
49
49
  ```
50
- usage: mposcli [-h] {build,cp,cp-app,flash,run-desktop,update,update-submodules,version}
50
+ usage: mposcli [-h] {build,cp,cp-app,flash,run-desktop,shell,update,update-
51
+ submodules,version}
51
52
 
52
53
 
53
54
 
@@ -61,8 +62,8 @@ usage: mposcli [-h] {build,cp,cp-app,flash,run-desktop,update,update-submodules,
61
62
  │ • cp Copy/update internal_filesystem/lib/mpos files to the device │
62
63
  │ via "mpremote fs cp". Display a file chooser to select which │
63
64
  │ files to copy/update. But can also be used to copy/update all │
64
- │ files. see: https://docs.micropythonos.com/os-development/insta
65
- lling-on-esp32/
65
+ │ files. see:
66
+ https://docs.micropythonos.com/architecture/filesystem/
66
67
  │ • cp-app Copy/update internal_filesystem/apps to the device via │
67
68
  │ "mpremote fs cp". Display a file chooser to select which app to │
68
69
  │ copy/update. But can also be used to copy/update all files. │
@@ -74,6 +75,9 @@ usage: mposcli [-h] {build,cp,cp-app,flash,run-desktop,update,update-submodules,
74
75
  │ opythonos.com/os-development/installing-on-esp32/ │
75
76
  │ • run-desktop Run MicroPythonOS on desktop. see: https://docs.micropythonos.c │
76
77
  │ om/getting-started/running/#running-on-desktop │
78
+ │ • shell Start a REPL shell connected to the device using mpremote. │
79
+ │ Optional reset before starting the REPL. The goal it to try to │
80
+ │ get a REPL in a loop until it works. │
77
81
  │ • update Update MicroPythonOS repository. Assume that there is a │
78
82
  │ "origin" and/or "upstream" remote configured. Will also ask if │
79
83
  │ you want to update the submodules as well, which is │
@@ -93,13 +97,13 @@ usage: mposcli [-h] {build,cp,cp-app,flash,run-desktop,update,update-submodules,
93
97
 
94
98
  [comment]: <> (✂✂✂ auto generated build start ✂✂✂)
95
99
  ```
96
- usage: mposcli build [-h] [{esp32,esp32s3,unix,macOS}] [-v]
100
+ usage: mposcli build [-h] [{esp32,esp32s3,unphone,unix,macOS}] [-v]
97
101
 
98
102
  Build MicroPythonOS by calling: ./scripts/build_mpos.sh <target> see:
99
103
  https://docs.micropythonos.com/os-development/
100
104
 
101
105
  ╭─ positional arguments ───────────────────────────────────────────────────╮
102
- │ [{esp32,esp32s3,unix,macOS}]
106
+ │ [{esp32,esp32s3,unphone,unix,macOS}]
103
107
  │ Target platform to build for. (default: unix) │
104
108
  ╰──────────────────────────────────────────────────────────────────────────╯
105
109
  ╭─ options ────────────────────────────────────────────────────────────────╮
@@ -119,7 +123,7 @@ usage: mposcli cp [-h] [CP OPTIONS]
119
123
 
120
124
  Copy/update internal_filesystem/lib/mpos files to the device via "mpremote fs cp". Display
121
125
  a file chooser to select which files to copy/update. But can also be used to copy/update
122
- all files. see: https://docs.micropythonos.com/os-development/installing-on-esp32/
126
+ all files. see: https://docs.micropythonos.com/architecture/filesystem/
123
127
 
124
128
  ╭─ positional arguments ─────────────────────────────────────────────────────────────────╮
125
129
  │ [{None}|PATH] Optional file or directory path. (default: None) │
@@ -173,7 +177,9 @@ https://docs.micropythonos.com/os-development/installing-on-esp32/
173
177
 
174
178
  ╭─ options ──────────────────────────────────────────────────────────────────────────────╮
175
179
  │ -h, --help show this help message and exit │
176
- │ --port STR Port used for esptool and mpremote (default: /dev/ttyUSB0)
180
+ │ --port {None}|STR Port used for esptool and mpremote, e.g.: "/dev/ttyUSB0" or
181
+ │ "/dev/ttyACM0" etc. Leave empty for autodetection (default: │
182
+ │ None) │
177
183
  │ --address STR Address (default: 0x0) │
178
184
  │ --flash-size STR Flash Size (default: detect) │
179
185
  │ --verify, --no-verify Verify after flashing? (default: True) │
@@ -213,6 +219,26 @@ started/running/#running-on-desktop
213
219
 
214
220
 
215
221
 
222
+ ## mposcli shell
223
+
224
+ [comment]: <> (✂✂✂ auto generated shell start ✂✂✂)
225
+ ```
226
+ usage: mposcli shell [-h] [--reset | --no-reset] [-v]
227
+
228
+ Start a REPL shell connected to the device using mpremote. Optional reset before starting
229
+ the REPL. The goal it to try to get a REPL in a loop until it works.
230
+
231
+ ╭─ options ────────────────────────────────────────────────────────────────────────╮
232
+ │ -h, --help show this help message and exit │
233
+ │ --reset, --no-reset Reset the device before starting the REPL? (default: False) │
234
+ │ -v, --verbosity Verbosity level; e.g.: -v, -vv, -vvv, etc. (repeatable) │
235
+ ╰──────────────────────────────────────────────────────────────────────────────────╯
236
+ ```
237
+ [comment]: <> (✂✂✂ auto generated shell end ✂✂✂)
238
+
239
+
240
+
241
+
216
242
  ## mposcli update
217
243
 
218
244
 
@@ -317,6 +343,13 @@ completion,test,update,update-readme-history,update-test-snapshot-files,version}
317
343
 
318
344
  [comment]: <> (✂✂✂ auto generated history start ✂✂✂)
319
345
 
346
+ * [v0.6.0](https://github.com/jedie/mposcli/compare/v0.5.0...v0.6.0)
347
+ * 2026-03-21 - NEW: "shell" to start the REPL
348
+ * [v0.5.0](https://github.com/jedie/mposcli/compare/v0.4.1...v0.5.0)
349
+ * 2026-03-08 - update README
350
+ * 2026-03-05 - Enhance "cp" command and auto restart "mpremote repl"
351
+ * 2026-03-03 - Refactor "cp" command
352
+ * 2026-03-03 - flash command: use port auto detection as default
320
353
  * [v0.4.1](https://github.com/jedie/mposcli/compare/v0.4.0...v0.4.1)
321
354
  * 2026-02-27 - Use "--force" for pulling submodules to overwrite local changes
322
355
  * [v0.4.0](https://github.com/jedie/mposcli/compare/v0.3.0...v0.4.0)
@@ -325,6 +358,9 @@ completion,test,update,update-readme-history,update-test-snapshot-files,version}
325
358
  * 2026-02-23 - Update requirements and fix code style
326
359
  * 2026-02-23 - "build" command: target as positional argument
327
360
  * 2026-02-23 - Expand "cp" command and allow optional filesystem path
361
+
362
+ <details><summary>Expand older history entries ...</summary>
363
+
328
364
  * [v0.3.0](https://github.com/jedie/mposcli/compare/v0.2.0...v0.3.0)
329
365
  * 2026-02-18 - Add "update" beside "update-submodules"
330
366
  * 2026-02-17 - Update requirements
@@ -333,9 +369,6 @@ completion,test,update,update-readme-history,update-test-snapshot-files,version}
333
369
  * 2026-02-16 - New CLI command: "cp" with convenience features.
334
370
  * 2026-02-16 - New command: "flash" with file selector
335
371
  * 2026-02-16 - Update README.md
336
-
337
- <details><summary>Expand older history entries ...</summary>
338
-
339
372
  * [v0.1.0](https://github.com/jedie/mposcli/compare/1695026...v0.1.0)
340
373
  * 2026-02-16 - Add "update-submodules" command
341
374
  * 2026-02-16 - Add "build" command
@@ -32,7 +32,8 @@ cd ~/MicroPythonOS
32
32
 
33
33
  [comment]: <> (✂✂✂ auto generated main help start ✂✂✂)
34
34
  ```
35
- usage: mposcli [-h] {build,cp,cp-app,flash,run-desktop,update,update-submodules,version}
35
+ usage: mposcli [-h] {build,cp,cp-app,flash,run-desktop,shell,update,update-
36
+ submodules,version}
36
37
 
37
38
 
38
39
 
@@ -46,8 +47,8 @@ usage: mposcli [-h] {build,cp,cp-app,flash,run-desktop,update,update-submodules,
46
47
  │ • cp Copy/update internal_filesystem/lib/mpos files to the device │
47
48
  │ via "mpremote fs cp". Display a file chooser to select which │
48
49
  │ files to copy/update. But can also be used to copy/update all │
49
- │ files. see: https://docs.micropythonos.com/os-development/insta
50
- lling-on-esp32/
50
+ │ files. see:
51
+ https://docs.micropythonos.com/architecture/filesystem/
51
52
  │ • cp-app Copy/update internal_filesystem/apps to the device via │
52
53
  │ "mpremote fs cp". Display a file chooser to select which app to │
53
54
  │ copy/update. But can also be used to copy/update all files. │
@@ -59,6 +60,9 @@ usage: mposcli [-h] {build,cp,cp-app,flash,run-desktop,update,update-submodules,
59
60
  │ opythonos.com/os-development/installing-on-esp32/ │
60
61
  │ • run-desktop Run MicroPythonOS on desktop. see: https://docs.micropythonos.c │
61
62
  │ om/getting-started/running/#running-on-desktop │
63
+ │ • shell Start a REPL shell connected to the device using mpremote. │
64
+ │ Optional reset before starting the REPL. The goal it to try to │
65
+ │ get a REPL in a loop until it works. │
62
66
  │ • update Update MicroPythonOS repository. Assume that there is a │
63
67
  │ "origin" and/or "upstream" remote configured. Will also ask if │
64
68
  │ you want to update the submodules as well, which is │
@@ -78,13 +82,13 @@ usage: mposcli [-h] {build,cp,cp-app,flash,run-desktop,update,update-submodules,
78
82
 
79
83
  [comment]: <> (✂✂✂ auto generated build start ✂✂✂)
80
84
  ```
81
- usage: mposcli build [-h] [{esp32,esp32s3,unix,macOS}] [-v]
85
+ usage: mposcli build [-h] [{esp32,esp32s3,unphone,unix,macOS}] [-v]
82
86
 
83
87
  Build MicroPythonOS by calling: ./scripts/build_mpos.sh <target> see:
84
88
  https://docs.micropythonos.com/os-development/
85
89
 
86
90
  ╭─ positional arguments ───────────────────────────────────────────────────╮
87
- │ [{esp32,esp32s3,unix,macOS}]
91
+ │ [{esp32,esp32s3,unphone,unix,macOS}]
88
92
  │ Target platform to build for. (default: unix) │
89
93
  ╰──────────────────────────────────────────────────────────────────────────╯
90
94
  ╭─ options ────────────────────────────────────────────────────────────────╮
@@ -104,7 +108,7 @@ usage: mposcli cp [-h] [CP OPTIONS]
104
108
 
105
109
  Copy/update internal_filesystem/lib/mpos files to the device via "mpremote fs cp". Display
106
110
  a file chooser to select which files to copy/update. But can also be used to copy/update
107
- all files. see: https://docs.micropythonos.com/os-development/installing-on-esp32/
111
+ all files. see: https://docs.micropythonos.com/architecture/filesystem/
108
112
 
109
113
  ╭─ positional arguments ─────────────────────────────────────────────────────────────────╮
110
114
  │ [{None}|PATH] Optional file or directory path. (default: None) │
@@ -158,7 +162,9 @@ https://docs.micropythonos.com/os-development/installing-on-esp32/
158
162
 
159
163
  ╭─ options ──────────────────────────────────────────────────────────────────────────────╮
160
164
  │ -h, --help show this help message and exit │
161
- │ --port STR Port used for esptool and mpremote (default: /dev/ttyUSB0)
165
+ │ --port {None}|STR Port used for esptool and mpremote, e.g.: "/dev/ttyUSB0" or
166
+ │ "/dev/ttyACM0" etc. Leave empty for autodetection (default: │
167
+ │ None) │
162
168
  │ --address STR Address (default: 0x0) │
163
169
  │ --flash-size STR Flash Size (default: detect) │
164
170
  │ --verify, --no-verify Verify after flashing? (default: True) │
@@ -198,6 +204,26 @@ started/running/#running-on-desktop
198
204
 
199
205
 
200
206
 
207
+ ## mposcli shell
208
+
209
+ [comment]: <> (✂✂✂ auto generated shell start ✂✂✂)
210
+ ```
211
+ usage: mposcli shell [-h] [--reset | --no-reset] [-v]
212
+
213
+ Start a REPL shell connected to the device using mpremote. Optional reset before starting
214
+ the REPL. The goal it to try to get a REPL in a loop until it works.
215
+
216
+ ╭─ options ────────────────────────────────────────────────────────────────────────╮
217
+ │ -h, --help show this help message and exit │
218
+ │ --reset, --no-reset Reset the device before starting the REPL? (default: False) │
219
+ │ -v, --verbosity Verbosity level; e.g.: -v, -vv, -vvv, etc. (repeatable) │
220
+ ╰──────────────────────────────────────────────────────────────────────────────────╯
221
+ ```
222
+ [comment]: <> (✂✂✂ auto generated shell end ✂✂✂)
223
+
224
+
225
+
226
+
201
227
  ## mposcli update
202
228
 
203
229
 
@@ -302,6 +328,13 @@ completion,test,update,update-readme-history,update-test-snapshot-files,version}
302
328
 
303
329
  [comment]: <> (✂✂✂ auto generated history start ✂✂✂)
304
330
 
331
+ * [v0.6.0](https://github.com/jedie/mposcli/compare/v0.5.0...v0.6.0)
332
+ * 2026-03-21 - NEW: "shell" to start the REPL
333
+ * [v0.5.0](https://github.com/jedie/mposcli/compare/v0.4.1...v0.5.0)
334
+ * 2026-03-08 - update README
335
+ * 2026-03-05 - Enhance "cp" command and auto restart "mpremote repl"
336
+ * 2026-03-03 - Refactor "cp" command
337
+ * 2026-03-03 - flash command: use port auto detection as default
305
338
  * [v0.4.1](https://github.com/jedie/mposcli/compare/v0.4.0...v0.4.1)
306
339
  * 2026-02-27 - Use "--force" for pulling submodules to overwrite local changes
307
340
  * [v0.4.0](https://github.com/jedie/mposcli/compare/v0.3.0...v0.4.0)
@@ -310,6 +343,9 @@ completion,test,update,update-readme-history,update-test-snapshot-files,version}
310
343
  * 2026-02-23 - Update requirements and fix code style
311
344
  * 2026-02-23 - "build" command: target as positional argument
312
345
  * 2026-02-23 - Expand "cp" command and allow optional filesystem path
346
+
347
+ <details><summary>Expand older history entries ...</summary>
348
+
313
349
  * [v0.3.0](https://github.com/jedie/mposcli/compare/v0.2.0...v0.3.0)
314
350
  * 2026-02-18 - Add "update" beside "update-submodules"
315
351
  * 2026-02-17 - Update requirements
@@ -318,9 +354,6 @@ completion,test,update,update-readme-history,update-test-snapshot-files,version}
318
354
  * 2026-02-16 - New CLI command: "cp" with convenience features.
319
355
  * 2026-02-16 - New command: "flash" with file selector
320
356
  * 2026-02-16 - Update README.md
321
-
322
- <details><summary>Expand older history entries ...</summary>
323
-
324
357
  * [v0.1.0](https://github.com/jedie/mposcli/compare/1695026...v0.1.0)
325
358
  * 2026-02-16 - Add "update-submodules" command
326
359
  * 2026-02-16 - Add "build" command
@@ -4,5 +4,5 @@
4
4
  """
5
5
 
6
6
  # See https://packaging.python.org/en/latest/specifications/version-specifiers/
7
- __version__ = '0.4.1'
7
+ __version__ = '0.6.0'
8
8
  __author__ = 'Jens Diemer <cookiecutter_templates@jensdiemer.de>'
@@ -18,7 +18,7 @@ logger = logging.getLogger(__name__)
18
18
  @app.command
19
19
  def build(
20
20
  target: Annotated[
21
- Literal['esp32', 'esp32s3', 'unix', 'macOS'],
21
+ Literal['esp32', 'esp32s3', 'unphone', 'unix', 'macOS'],
22
22
  tyro.conf.arg(
23
23
  help='Target platform to build for.',
24
24
  ),
@@ -14,6 +14,7 @@ from mposcli.cli_app import app
14
14
  from mposcli.mpos_utils import get_mpos_path
15
15
  from mposcli.tools import get_mpremote_bin
16
16
  from mposcli.user_input import choose_newest_modified_directory, get_newest_files
17
+ from mposcli.utilities.mpremote import MpOsPathResolver, start_mpremote_repl
17
18
 
18
19
 
19
20
  logger = logging.getLogger(__name__)
@@ -44,58 +45,45 @@ def cp(
44
45
  Copy/update internal_filesystem/lib/mpos files to the device via "mpremote fs cp".
45
46
  Display a file chooser to select which files to copy/update.
46
47
  But can also be used to copy/update all files.
47
- see: https://docs.micropythonos.com/os-development/installing-on-esp32/
48
+ see: https://docs.micropythonos.com/architecture/filesystem/
48
49
  """
49
50
  setup_logging(verbosity=verbosity)
50
51
 
51
52
  mpos_path = get_mpos_path()
52
-
53
- internal_fs = mpos_path / 'internal_filesystem'
54
- lib_mpos = internal_fs / 'lib' / 'mpos'
55
- assert_is_dir(lib_mpos)
56
- apps_path = internal_fs / 'apps'
57
- assert_is_dir(apps_path)
53
+ resolver = MpOsPathResolver(mpos=mpos_path)
58
54
 
59
55
  mpremote_bin = get_mpremote_bin()
60
56
 
61
57
  print('\n')
62
58
 
63
- popenargs = (mpremote_bin, 'fs', 'cp')
64
-
65
- if local_path:
66
- if not local_path.is_absolute():
67
- local_path = mpos_path / local_path
59
+ if not local_path:
60
+ # Let's the user select from the list of the newest modified files in internal_filesystem/lib/mpos:
61
+ local_path = get_newest_files(resolver.lib_mpos, limit=new_file_limit)
62
+ if not local_path:
63
+ print('Copy/update all files in lib/mpos to the device')
64
+ local_path = resolver.lib_mpos
68
65
 
69
- print(f'Copy/update app: "{local_path}" ...')
66
+ print(f'Copy/update app: "{local_path}" ...')
70
67
 
71
- if not local_path.exists():
72
- print(f'[red]Error: The specified source path "{local_path}" does not exist.[/red]')
73
- return
68
+ if not local_path.exists():
69
+ print(f'[red]Error: The specified source path "{local_path}" does not exist.[/red]')
70
+ return
74
71
 
75
- # Is source a sub path of "apps_path" ?
76
- if local_path.is_relative_to(apps_path):
77
- local_rel_path = local_path.relative_to(mpos_path)
78
- remote_path = f':/{local_path.relative_to(internal_fs)}'
79
- else:
80
- raise NotImplementedError
81
- else:
82
- local_path = get_newest_files(lib_mpos, limit=new_file_limit)
83
- if not local_path:
84
- print('Copy/update all files in lib/mpos to the device')
85
- local_path = lib_mpos
72
+ local_path_str, remote_str = resolver.resolve(local_path)
86
73
 
87
- local_rel_path = local_path.relative_to(mpos_path)
88
- remote_path = f':/{local_path.relative_to(lib_mpos.parent)}'
74
+ popenargs = (mpremote_bin, 'fs')
89
75
 
90
76
  if local_path.is_dir():
91
77
  popenargs += ('-r',)
92
- print(f'Copying directory "[bold]{local_rel_path}[/bold]" to device at "[bold]{remote_path}[/bold]" ...')
78
+ print(f'Copying directory "[bold]{local_path_str}[/bold]" to device at "[bold]{remote_str}[/bold]" ...')
93
79
  else:
94
- print(f'Copying file "[bold]{local_rel_path}[/bold]" to device at "[bold]{remote_path}[/bold]" ...')
80
+ print(f'Copying file "[bold]{local_path_str}[/bold]" to device at "[bold]{remote_str}[/bold]" ...')
95
81
 
96
- popenargs += (local_rel_path, remote_path)
97
82
  verbose_check_call(
98
83
  *popenargs,
84
+ 'cp',
85
+ local_path_str,
86
+ remote_str,
99
87
  verbose=True,
100
88
  cwd=mpos_path,
101
89
  text=None,
@@ -112,15 +100,7 @@ def cp(
112
100
  )
113
101
 
114
102
  if repl:
115
- time.sleep(1)
116
- verbose_check_call(
117
- mpremote_bin,
118
- 'repl',
119
- verbose=True,
120
- cwd=mpos_path,
121
- timeout=None,
122
- text=None,
123
- )
103
+ start_mpremote_repl()
124
104
 
125
105
 
126
106
  @app.command
@@ -158,14 +138,14 @@ def cp_app(
158
138
 
159
139
  if not app:
160
140
  print('Copy/update all apps in "internal_filesystem/apps" to the device')
161
- local_rel_path = 'internal_filesystem/apps'
141
+ local_path = 'internal_filesystem/apps'
162
142
  remote_path = ':/apps'
163
143
  else:
164
144
  print(f'Copy/update {app=} ...')
165
- local_rel_path = f'internal_filesystem/apps/{app.name}'
145
+ local_path = f'internal_filesystem/apps/{app.name}'
166
146
  remote_path = f':/apps/{app.name}'
167
147
 
168
- popenargs += (local_rel_path, remote_path)
148
+ popenargs += (local_path, remote_path)
169
149
  verbose_check_call(
170
150
  *popenargs,
171
151
  verbose=True,
@@ -18,7 +18,15 @@ logger = logging.getLogger(__name__)
18
18
 
19
19
  @app.command
20
20
  def flash(
21
- port: Annotated[str, tyro.conf.arg(help='Port used for esptool and mpremote')] = '/dev/ttyUSB0',
21
+ port: Annotated[
22
+ str | None,
23
+ tyro.conf.arg(
24
+ help=(
25
+ 'Port used for esptool and mpremote, e.g.: "/dev/ttyUSB0" or "/dev/ttyACM0" etc.'
26
+ ' Leave empty for autodetection'
27
+ )
28
+ ),
29
+ ] = None,
22
30
  address: Annotated[str, tyro.conf.arg(help='Address')] = '0x0',
23
31
  flash_size: Annotated[str, tyro.conf.arg(help='Flash Size')] = 'detect',
24
32
  verify: Annotated[bool, tyro.conf.arg(help='Verify after flashing?')] = True,
@@ -46,10 +54,12 @@ def flash(
46
54
  image_files = lvgl_micropython_build_path.glob('*.bin')
47
55
  image_file = file_chooser(image_files)
48
56
 
57
+ popenargs = (esptool_bin,)
58
+ if port:
59
+ popenargs += ('--port', port)
60
+
49
61
  verbose_check_call(
50
- esptool_bin,
51
- '--port',
52
- port,
62
+ *popenargs,
53
63
  'write-flash',
54
64
  '--flash-size',
55
65
  flash_size,
@@ -63,9 +73,7 @@ def flash(
63
73
 
64
74
  if verify:
65
75
  verbose_check_call(
66
- esptool_bin,
67
- '--port',
68
- port,
76
+ *popenargs,
69
77
  'verify-flash',
70
78
  address,
71
79
  image_file,
@@ -0,0 +1,45 @@
1
+ import logging
2
+ import time
3
+ from typing import Annotated
4
+
5
+ import tyro
6
+ from cli_base.cli_tools.subprocess_utils import verbose_check_call
7
+ from cli_base.cli_tools.verbosity import setup_logging
8
+ from cli_base.tyro_commands import TyroVerbosityArgType
9
+
10
+ from mposcli.cli_app import app
11
+ from mposcli.mpos_utils import get_mpos_path
12
+ from mposcli.tools import get_mpremote_bin
13
+ from mposcli.utilities.mpremote import start_mpremote_repl
14
+
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+
19
+ @app.command
20
+ def shell(
21
+ reset: Annotated[
22
+ bool,
23
+ tyro.conf.arg(help='Reset the device before starting the REPL?'),
24
+ ] = False,
25
+ verbosity: TyroVerbosityArgType = 1,
26
+ ):
27
+ """
28
+ Start a REPL shell connected to the device using mpremote. Optional reset before starting the REPL.
29
+ The goal it to try to get a REPL in a loop until it works.
30
+ """
31
+ setup_logging(verbosity=verbosity)
32
+ mpos_path = get_mpos_path()
33
+ mpremote_bin = get_mpremote_bin()
34
+
35
+ if reset:
36
+ time.sleep(1)
37
+ verbose_check_call(
38
+ mpremote_bin,
39
+ 'reset',
40
+ verbose=True,
41
+ cwd=mpos_path,
42
+ text=None,
43
+ )
44
+
45
+ start_mpremote_repl()
@@ -0,0 +1,74 @@
1
+ import tempfile
2
+ from pathlib import Path
3
+ from unittest import TestCase
4
+
5
+ from mposcli.utilities.mpremote import MpOsPathResolver
6
+
7
+
8
+ class ProjectSetupTestCase(TestCase):
9
+ def test_mpos_path_resolver(self):
10
+ with tempfile.TemporaryDirectory() as temp_dir:
11
+ mpos_path = Path(temp_dir).resolve()
12
+
13
+ camera_assets = mpos_path / 'internal_filesystem/apps/com.micropythonos.camera/assets/'
14
+ camera_assets.mkdir(parents=True)
15
+ (camera_assets / 'camera_app.py').touch()
16
+
17
+ board_path = mpos_path / 'internal_filesystem/lib/mpos/board/'
18
+ board_path.mkdir(parents=True)
19
+ (board_path / 'unphone.py').touch()
20
+
21
+ (mpos_path / 'internal_filesystem/lib/drivers/display/hx8357d/').mkdir(parents=True)
22
+
23
+ resolver = MpOsPathResolver(mpos=mpos_path)
24
+ self.assertEqual(
25
+ resolver.resolve(mpos_path / 'internal_filesystem/apps/com.micropythonos.camera'),
26
+ (
27
+ 'internal_filesystem/apps/com.micropythonos.camera/',
28
+ ':apps/',
29
+ ),
30
+ )
31
+ self.assertEqual(
32
+ resolver.resolve(mpos_path / 'internal_filesystem/apps/com.micropythonos.camera/assets/'),
33
+ (
34
+ 'internal_filesystem/apps/com.micropythonos.camera/assets/',
35
+ ':apps/com.micropythonos.camera/',
36
+ ),
37
+ )
38
+ self.assertEqual(
39
+ resolver.resolve(mpos_path / 'internal_filesystem/apps/com.micropythonos.camera/assets/camera_app.py'),
40
+ (
41
+ 'internal_filesystem/apps/com.micropythonos.camera/assets/camera_app.py',
42
+ ':apps/com.micropythonos.camera/assets/camera_app.py',
43
+ ),
44
+ )
45
+
46
+ self.assertEqual(
47
+ resolver.resolve(mpos_path / 'internal_filesystem/lib/mpos/board/'),
48
+ (
49
+ 'internal_filesystem/lib/mpos/board/',
50
+ ':mpos/',
51
+ ),
52
+ )
53
+ self.assertEqual(
54
+ resolver.resolve(mpos_path / 'internal_filesystem/lib/mpos/board/unphone.py'),
55
+ (
56
+ 'internal_filesystem/lib/mpos/board/unphone.py',
57
+ ':mpos/board/unphone.py',
58
+ ),
59
+ )
60
+
61
+ self.assertEqual(
62
+ resolver.resolve(mpos_path / 'internal_filesystem/lib/drivers/display/hx8357d'),
63
+ (
64
+ 'internal_filesystem/lib/drivers/display/hx8357d/',
65
+ ':lib/drivers/display/',
66
+ ),
67
+ )
68
+ self.assertEqual(
69
+ resolver.resolve(mpos_path / 'internal_filesystem/lib/drivers/display'),
70
+ (
71
+ 'internal_filesystem/lib/drivers/display/',
72
+ ':lib/drivers/',
73
+ ),
74
+ )
@@ -83,7 +83,19 @@ class ReadmeTestCase(BaseTestCase):
83
83
 
84
84
  commands.discard('version') # version is pseudo command, because the version always printed on every CLI call
85
85
  commands = sorted(commands)
86
- self.assertEqual(commands, ['build', 'cp', 'cp-app', 'flash', 'run-desktop', 'update', 'update-submodules'])
86
+ self.assertEqual(
87
+ commands,
88
+ [
89
+ 'build',
90
+ 'cp',
91
+ 'cp-app',
92
+ 'flash',
93
+ 'run-desktop',
94
+ 'shell',
95
+ 'update',
96
+ 'update-submodules',
97
+ ],
98
+ )
87
99
 
88
100
  for command in commands:
89
101
  with self.subTest(command):
@@ -1,3 +1,4 @@
1
+ import functools
1
2
  import shutil
2
3
  import sys
3
4
  from pathlib import Path
@@ -15,12 +16,14 @@ def get_bin(name: str) -> Path:
15
16
  return Path(bin_path)
16
17
 
17
18
 
19
+ @functools.cache
18
20
  def get_esptool_bin() -> Path:
19
21
  esptool_bin = get_bin('esptool')
20
22
  verbose_check_call(esptool_bin, 'version')
21
23
  return esptool_bin
22
24
 
23
25
 
26
+ @functools.cache
24
27
  def get_mpremote_bin():
25
28
  mpremote_bin = get_bin('mpremote')
26
29
  verbose_check_call(mpremote_bin, '--version')
@@ -46,7 +46,7 @@ def file_chooser(paths) -> Path | None:
46
46
  return selection
47
47
 
48
48
 
49
- def get_newest_files(directory, limit=10) -> Path | None:
49
+ def get_newest_files(directory: Path, limit=10) -> Path | None:
50
50
  files = []
51
51
 
52
52
  def scan(dir_path):
@@ -60,7 +60,7 @@ def get_newest_files(directory, limit=10) -> Path | None:
60
60
  scan(directory)
61
61
  files.sort(key=lambda x: x[1], reverse=True)
62
62
 
63
- print(f'[bold]Choose a file[/bold] (only from the newest {limit}):\n')
63
+ print(f'[bold]Choose a file[/bold] (only from the newest {limit} from {directory}):\n')
64
64
  for idx, (entry, mtime) in enumerate(files[:limit]):
65
65
  dt = datetime.datetime.fromtimestamp(mtime).astimezone().strftime('%Y-%m-%d %H:%M:%S')
66
66
  rel_path = Path(entry.path).relative_to(directory)
@@ -85,8 +85,9 @@ def get_newest_files(directory, limit=10) -> Path | None:
85
85
  print(f'[red]Invalid selection: {number}[/red]')
86
86
  sys.exit(1)
87
87
 
88
- print(f'Selected file: {selection}')
89
- return Path(selection)
88
+ absolute_path = directory / selection
89
+ print(f'Selected file: {absolute_path}')
90
+ return absolute_path
90
91
 
91
92
 
92
93
  def choose_newest_modified_directory(base_dir: Path) -> Path:
File without changes
@@ -0,0 +1,64 @@
1
+ import shlex
2
+ import subprocess
3
+ import time
4
+ from pathlib import Path
5
+
6
+ from bx_py_utils.path import assert_is_dir
7
+ from rich import print
8
+
9
+ from mposcli.tools import get_mpremote_bin
10
+
11
+
12
+ class MpOsPathResolver:
13
+ def __init__(self, mpos: Path):
14
+ self.mpos = mpos # e.g.: ~/repos/MicroPythonOS
15
+ self.internal_fs = mpos / 'internal_filesystem'
16
+ self.lib_mpos = self.internal_fs / 'lib' / 'mpos'
17
+ assert_is_dir(self.lib_mpos)
18
+ self.apps_path = self.internal_fs / 'apps'
19
+ assert_is_dir(self.apps_path)
20
+
21
+ def resolve(self, source: Path):
22
+ source = source.resolve()
23
+ assert source.exists(), f'Not existing path: {source=}'
24
+ assert source.is_relative_to(self.mpos), f'{source=} is not inside {self.mpos=}'
25
+
26
+ if source.is_relative_to(self.apps_path):
27
+ device_base_path = self.apps_path.parent
28
+ elif source.is_relative_to(self.lib_mpos):
29
+ device_base_path = self.lib_mpos.parent
30
+ elif source.is_relative_to(self.internal_fs):
31
+ device_base_path = self.internal_fs
32
+ else:
33
+ raise ValueError(f'Path {source} is not in a recognized location')
34
+
35
+ remote_path = source.relative_to(device_base_path)
36
+ source_path = source.relative_to(self.mpos)
37
+ if source.is_dir():
38
+ remote_path = remote_path.parent
39
+ local_path_str = f'{source_path}/'
40
+ remote_str = f':{remote_path}/'
41
+ else:
42
+ local_path_str = f'{source_path}'
43
+ remote_str = f':{remote_path}'
44
+
45
+ return local_path_str, remote_str
46
+
47
+
48
+ def start_mpremote_repl(max_try=10, wait_time=1):
49
+ print('Starting mpremote REPL...')
50
+ time.sleep(wait_time)
51
+ mpremote_bin = get_mpremote_bin()
52
+
53
+ popen_args = (mpremote_bin, 'repl')
54
+
55
+ for try_count in range(max_try):
56
+ print(f'\n+ {shlex.join(str(arg) for arg in popen_args)}')
57
+ try:
58
+ return subprocess.check_call(popen_args)
59
+ except subprocess.CalledProcessError as err:
60
+ print(
61
+ f'[yellow]mpremote finished with [red]exit code {err.returncode!r}[/red]'
62
+ f' Retrying in {wait_time} seconds... (try {try_count + 1}/{max_try})'
63
+ )
64
+ time.sleep(wait_time)
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes