Skip to content

Commit b6e4e63

Browse files
committed
Updated API, refactored examples and App Manager.
Signed-off-by: ubi de feo <me@ubidefeo.com>
1 parent 437aaca commit b6e4e63

26 files changed

+434
-236
lines changed

README.md

Lines changed: 52 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,39 @@
11
# Arduino Tools for MicroPython
22

33
This package adds functionalities for
4+
45
* MicroPython apps framework
56
* file system helpers
67
* WiFi network management
78

9+
## Installation
10+
11+
### Using `mpremote`
12+
13+
We must specify the target especially if the framework is already installed and a default app is present, since `mpremote mip` will use the current path to look for or create the `lib` folder.
14+
We want to make sure the tools are accessible from every application/location.
15+
16+
```bash
17+
mpremote mip install --target=[flash]/lib github:arduino/arduino-tools-mpy
18+
```
19+
20+
### Using `mip` from the board.
21+
22+
First make sure your board is connected to a network, or `mip` will fail.
23+
24+
```python
25+
import mip
26+
mip.install('github:arduino/arduino-tools-mpy', target='[flash]/lib')
27+
```
828

929
## MicroPython Apps Framework
30+
1031
A set of tools and helpers to implement, create and manage MicroPython Apps.
1132

1233
A new approach to enabling a MicroPython board to host/store multiple projects with the choice of running one as default, as well as have a mechanism of fallback to a default launcher.
1334
It does not interfere with the canonical `boot.py` > `main.py` run paradigm, and allows users to easily activate this functionality on top of any stock MicroPython file-system.
1435

15-
The Arduino MicroPython App framework relies on the creation of well structured projects/apps enclosed in their own folders named "app_{app-name}", which in turn contain a set of files (`main.py`, `lib/`, `app.json`, etc.).
36+
The Arduino MicroPython App framework relies on the creation of aptly structured projects/apps enclosed in their own folders named "app_{app-name}", which in turn contain a set of files (`main.py`, `lib/`, `app.json`, etc.).
1637
These are the conditions for a project/app to be considered "valid".
1738
Other files can be added to user's discretion, for instance to store assets or log/save data.
1839

@@ -23,44 +44,50 @@ The framework exploits the standard behaviour of MicroPython at start/reset/soft
2344

2445
The framework's boot.py only requires two lines for the following operations:
2546

26-
- import the minimum required parts of arduino_tools (common) from the board's FileSystem (preferrably installed as a module in /lib/arduino_tools)
27-
- call a method to enter the default app's path and apply some temporary settings to configure the running environment (search paths and launch configuration changes) which will be reset at the next start.
47+
* import the minimum required parts of arduino_tools (common) from the board's File System (installed as a package in [flash]/lib/arduino_tools)
48+
* invoke the method `load_app()` to enter the default app's path and apply some temporary settings to configure the running environment (search paths and launch configuration changes) which will be reset at the next start.
2849

2950
If no default app is set, it will fall back to the `main.py` in the board's root if present.
30-
No error condition will be generated, as MicroPython is capable of handling the absence of `boot.py` and/or `main.py`.
51+
No error condition will be generated, as MicroPython is capable of handling the absence of `boot.py` and/or `main.py` at C level.
3152

32-
If a default app is set, the `enter_default_app()` will issue an `os.chdir()` command and enter the app's folder.
53+
If a default app is set, the `load_app()` will issue an `os.chdir()` command and enter the app's folder.
3354
MicroPython will automatically run the main.py it finds in its Current Working Directory.
3455

3556
**NOTES:**
3657

37-
- each app can contain a `.hidden` file that will hide the app from AMP, effectively preventing listing or deletion.
58+
* each app can contain a `.hidden` file that will hide the app from AMP, effectively preventing listing or deletion.
3859
The `list_apps()` command accepts a `skip_hidden = False` parameter to return every app, not just the visible ones.
3960

40-
- each app should contain a metadata file named `app.json`
41-
{
42-
"name": "",
43-
"author": "",
44-
"created": 0,
45-
"modified": 0,
46-
"version": "x.y.z",
47-
"origin_url": "",
48-
"tools_version": "x.y.z"
49-
}
50-
- while some fields should be mandatory ("name", "hidden") others could not be a requirement, especially for students apps who do not care about versions or source URL.
51-
We should also handle if extra fields are added not to break legacy
52-
53-
- AMP can replace/update an app with the content of a `.tar` archive.
54-
This is useful for updating versions of apps/launcher/demos.
61+
* each app should contain a metadata file named `app.json`
62+
63+
```json
64+
{
65+
"name": "",
66+
"friendly_name": "",
67+
"author": "",
68+
"created": 0,
69+
"modified": 0,
70+
"version": "M.m.p",
71+
"origin_url": "https://arduino.cc",
72+
"tools_version": "M.m.p"
73+
}
74+
```
75+
76+
* while some fields should be mandatory ("name", "tools_version") others could not be required, especially for students apps who do not care about versions or source URL.
77+
We should also handle if extra fields are added not to break legacy.
78+
Still WIP.
79+
80+
* AMP can replace/update an app with the contents of a properly structured `.tar` archive.
81+
This is useful for updating versions of apps/launcher.
5582
An app launcher could be delegated to checking for available updates to any of the other apps it manages.
5683

5784
### How to setup
5885

5986
**NOTE:** The API is not yet final, hence subject to changes.
60-
Same goes for the name of the module(s)
87+
Same goes for the name of modules.
6188

6289
The only requirement is that all the files in `arduino_tools` should be transferred to the board using one's preferred method.
63-
Best practice is to copy all the files in the board's `/lib/arduino_tools`, which is what happens when installing with the `mip` tool.
90+
Best practice is to copy all the files in the board's `[flash]/lib/arduino_tools`, which is what happens when installing with the `mip` tool (or `mpremote mip`).
6491

6592
Enter a REPL session
6693

@@ -101,7 +128,7 @@ Note: creating an app and giving it a name with unallowed characters will replac
101128
Enable AMP and create a few apps
102129

103130
```shell
104-
>>> from arduino_tools.app_manager import *
131+
>>> from arduino_tools.apps_manager import *
105132
>>> enable_apps()
106133

107134
>>> create_app('abc')
@@ -138,7 +165,7 @@ Type "help()" for more information.
138165

139166
### Advanced Usage
140167

141-
#### Restore script
168+
#### Restore script (very experimental)
142169

143170
The restore script allows to have a way of restoring a default app at boot.
144171
This script may respond to a hardware condition such as a pressed pin in order to set the default app to run.

arduino_tools/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from .constants import TOOLS_VERSION
22
from .loader import *
3+
from .helpers import show_commands
34
__author__ = "Ubi de Feo"
45
__credits__ = ["Ubi de Feo", "Sebastian Romero"]
56
__license__ = "MPL 2.0"

arduino_tools/app.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from .common import validate_app
22
from .loader import enter_app
33
from .properties import get_app_properties, update_app_properties
4+
from .apps_manager import import_app
45
import os
56

67
class App:
@@ -26,8 +27,10 @@ def save_properties(self):
2627
def get_path(self):
2728
return self.get_property('path')
2829

29-
def update_app(self):
30-
self.app_updater = __import__('arduino_tools.updater')
31-
updater = __import__('arduino_tools.updater')
32-
updater.updater.check_for_updates(self.app_name)
33-
30+
def update_app(self, path = None):
31+
if path is not None:
32+
self.app_updater = __import__('arduino_tools.updater')
33+
updater = __import__('arduino_tools.updater')
34+
updater.updater.check_for_updates(self.app_name)
35+
else:
36+
import_app(path)

arduino_tools/apps_manager.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ def install_package(package = None, app = None, url = None):
9292
# Managing apps
9393

9494
def create_app(app_name = None, friendly_name = None, set_default = False, hidden = False):
95-
a_name = app_name or 'untitled'
95+
a_name = app_name or f'py_{tm()}'
9696
a_name = "".join(c for c in a_name if c.isalpha() or c.isdigit() or c==' ' or c == '_').rstrip()
9797
a_name = a_name.replace(' ', '_')
9898
if validate_app(app_name):
@@ -250,7 +250,6 @@ def set_required_app_properties(app_name, **keys):
250250
if not validate_app(app_name) :
251251
raise ValueError(f'Invalid app: {app_name}')
252252

253-
254253
def list_apps(return_list = False, include_hidden = False):
255254
apps_list = []
256255
for app in get_apps():

arduino_tools/boot_apps.tpl

Lines changed: 0 additions & 2 deletions
This file was deleted.

arduino_tools/common.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -34,23 +34,23 @@ def validate_app(app_name):
3434
else:
3535
return False
3636

37-
def default_app(app = None, fall_back = None):
38-
default_app = '' if app == None else app
39-
if app != None:
40-
if (not validate_app(default_app)) and default_app != '':
41-
return(OSError(9, f'Project {default_app} does not exist'))
37+
def default_app(app_name = None, fall_back = None):
38+
default_app_name = '' if app_name == None else app_name
39+
if app_name != None:
40+
if (not validate_app(default_app_name)) and default_app_name != '':
41+
return(OSError(9, f'Project {default_app_name} does not exist'))
4242
with open(APPS_ROOT + BOOT_CONFIG_FILE, 'w') as a_cfg:
43-
a_cfg.write(default_app)
43+
a_cfg.write(default_app_name)
4444
if fall_back != None:
4545
a_cfg.write('\n')
4646
a_cfg.write(fall_back)
4747
else:
4848
if fs_item_exists(APPS_ROOT + BOOT_CONFIG_FILE):
4949
with open(APPS_ROOT + BOOT_CONFIG_FILE, 'r') as a_cfg:
50-
default_app = a_cfg.readline().strip()
50+
default_app_name = a_cfg.readline().strip()
5151
else:
52-
default_app = None
53-
return default_app if default_app != None else None
52+
default_app_name = ''
53+
return default_app_name if default_app_name != '' else None
5454

5555
# more targeted approach
5656
def get_app(app_name):

arduino_tools/files.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
from time import time as tm
22
import os
3-
3+
from .common import get_root
44
def get_template_path(file_name):
5-
template_path = '/'.join(__file__.split('/')[:-1]) + f'/{file_name}'
5+
template_path = get_root().join(__file__.split('/')[:-1]) + f'/templates/{file_name}'
66
return template_path
77

8-
98
def template_to_file(template_name, destination_file, **variables):
109
template_path = get_template_path(template_name)
1110
try:
@@ -20,7 +19,6 @@ def template_to_file(template_name, destination_file, **variables):
2019
return False, f'{destination_file} not created', e
2120
return True, f'{destination_file} created', None
2221

23-
2422
### UNUSED UNTIL EXAMPLES LOADING IS IMPLEMENTED
2523
def new_file_from_source(file_name = None, destination_path = '.', overwrite = False, source_path = None):
2624
if file_name is None:

arduino_tools/help.txt

Lines changed: 84 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,13 @@ enable_apps():
44
disable_apps():
55
Disable support for Arduino MicroPython Apps.
66

7-
create_app('{app name}', set_default = False, hidden = False):
8-
Creates a app with the given name. No special characters allowed. Spaces will be converted to '_'
7+
create_app('{app name}', friendly_name = '{friendly_name}', set_default = False, hidden = False):
8+
Creates an app with the given name. No special characters allowed. Spaces will be converted to '_'
99
Setting the app as default will make it run at boot/reset.
1010
Making the app hidden will prevent its listing.
1111
Useful for launchers and other management utilities not to be messed with.
1212

13-
delete_app([force_confirm = 'n']):
13+
delete_app('{app_name}', force_confirm = False):
1414
Deletes the app with the given name.
1515
If force_confirm is set to 'Y' no confirmation will be required to delete the whole tree.
1616

@@ -29,31 +29,97 @@ get_apps_list(include_hidden = False)
2929
Path, name, default, hidden.
3030
Hidden apps are excluded by default.
3131

32-
default_app():
33-
Displays the app currently set as default (if any)
32+
default_app(app name = '', fall_back = None):
33+
If app name is '' (empty string) or not passed at all, no default will be set.
34+
If fall_back is set (and is a valid app based on name), at the next reset/boot that will become the default app to run.
35+
When no parameter is passed, the function returns the current default app or `None`
3436

35-
default_app('{app name}', fall_back = None):
36-
If app name is '' (empty string), no default will be set.
37-
If fall_back is set (and is a valid app), at the next reset/boot that will become the default app to run.
38-
39-
export_app('{app name}'):
37+
export_app('{app name}') - requires `tarfile-write` to be installed:
4038
Creates a .tar archive of the app with the given name if valid.
41-
The archive file will be named appending a timestamp to the app name.
42-
The archive will be saved in '/amp_exports/filename.tar'
39+
The archive file will be named appending a timestamp to the app's name.
40+
The archive will be saved in '/__ampexports/filename.tar'
4341

4442
import_app('{archive path}', force_overwrite = False):
4543
Expands the .tar archive into a app folder.
4644
Requires confirmation if app exists unless force_overwrite is True.
4745

48-
delete_folder('{folder path}', [force_confirm = 'n']):
46+
delete_folder('{folder path}', [force_confirm = False]):
4947
Will attempt to delete the folder and all its content recursively.
5048
If force_confirm is set to True it will not ask for confirmation at each file/folder.
5149

52-
list_directory('{folder path}'):
53-
[helper] Will recursively list through the path specified, indicationg if the item is a directory (d:) or a file (f:)
50+
read_file('{file path}'):
51+
Will read the content of the file and output it to REPL
52+
53+
--- WiFi Utilities ---
54+
55+
auto_connect(progress_callback = None):
56+
Automatically connects to known networks. Returns True on success.
57+
Optional progress_callback function for connection updates.
58+
59+
connect(ssid = None, key = None, interface = None, timeout = 10, display_progress = False, progress_callback = None):
60+
Connect to WiFi network. If no params provided, tries auto_connect then interactive mode.
61+
Returns network interface on success, None on failure.
62+
63+
connect_to_network(id = None):
64+
Interactive WiFi connection. Lists available networks if no id provided.
65+
Prompts for password and saves credentials on successful connection.
66+
67+
wifi_scan(force_scan = False):
68+
Scans for available WiFi networks. Uses cache unless force_scan=True.
69+
Returns list of network tuples (ssid, mac, channel, rssi, security, hidden).
70+
71+
list_networks(rescan = False):
72+
Lists available WiFi networks with formatted output.
73+
Rescans if rescan=True.
74+
75+
print_scan_results():
76+
Pretty prints all scanned networks (or stored one if within `_NETWORK_CACHE_LIFE`)
77+
78+
get_network_qrcode_string(ssid, password, security):
79+
Returns QR code string for WiFi network sharing.
80+
81+
print_network_details(interface = _net_if):
82+
Prints network connection details (IP, MAC, gateway, etc.).
83+
`interface` defaults to `_net_if` which is `None` when not initialised.
84+
85+
--- Access Point Mode ---
86+
87+
init_ap(ssid, key):
88+
Initializes device as WiFi Access Point with given credentials.
89+
90+
get_ap_settings():
91+
retrieves Access Point configuration
92+
93+
94+
--- File System Helpers ---
5495

5596
fs_root():
56-
[helper] Will change the current directory to root ('/')
97+
Changes current directory to filesystem root ('/').
5798

58-
read_file('{file path}'):
59-
Will read the content of the file and output it to REPL
99+
fs_getpath():
100+
Returns current working directory path.
101+
102+
print_directory(path = '.'):
103+
Prints formatted directory tree structure.
104+
105+
get_directory_tree(path = '.', order = -1):
106+
Returns directory tree as list of tuples (depth, path, name, is_folder).
107+
108+
is_directory(path):
109+
Returns True if path is a directory, False if file.
110+
111+
--- Console Utilities ---
112+
113+
show_cursor(show = True):
114+
Show or hide terminal cursor.
115+
116+
clear_terminal(cursor_visible = True):
117+
Clear terminal screen and optionally set cursor visibility.
118+
119+
show_commands():
120+
Display this help text in terminal.
121+
122+
--- Hash Utilities ---
123+
124+
get_hash(file_path):
125+
Calculate SHA256 hash of file content.

0 commit comments

Comments
 (0)