Skip to content

Commit 4207671

Browse files
Update docs
1 parent 5dc72d7 commit 4207671

13 files changed

Lines changed: 328 additions & 96 deletions

File tree

docs/apps/bundling-apps.md

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,41 @@
11
# Bundling Apps
22

3-
To bundle your app in an .mpk file, just make an uncompressed "zip" file of it, without including the top-level `com.micropythonos.helloworld/` folder.
3+
Apps are distributed as `.mpk` files. An `.mpk` is a ZIP archive (usually stored without compression to speed up installation on device) with a strict layout:
44

5-
It's recommended to make the .mpk file deterministic by:
6-
- setting the file timestamps to a fixed value
7-
- sorting the files, so the order is fixed
8-
- excluding extra file attributes and directories
5+
- The **first entry** in the ZIP stream **must** be a top-level directory whose name matches the app's fullname exactly, followed by `/` (for example, `com.micropythonos.helloworld/`).
6+
- That top-level directory **must** be the only top-level entry in the archive.
7+
- All other files and subdirectories live under that single top-level directory.
98

10-
For example:
9+
So a valid archive looks like this in stream order:
1110

1211
```
13-
cd com.micropythonos.helloworld/
14-
find . -type f -exec touch -t 202501010000.00 {} \; # set fixed timestamp to have a deterministic zip file
15-
find . -type f | sort | TZ=CET zip -X -r -0 /tmp/com.micropythonos.helloworld_0.0.2.mpk -@ # sort, -Xclude extra attributes, -recurse into directories and -0 compression
12+
com.micropythonos.helloworld/
13+
com.micropythonos.helloworld/MANIFEST.JSON
14+
com.micropythonos.helloworld/icon_64x64.png
15+
com.micropythonos.helloworld/hello.py
1616
```
1717

18+
MicroPythonOS validates this layout while extracting, and rejects packages that do not follow it. This ensures packages are unambiguous and can be streamed safely onto devices with limited storage.
19+
20+
## Creating an .mpk
21+
22+
From the parent directory that contains your app folder, create a stored (uncompressed) ZIP that includes the top-level folder. It's recommended to make the `.mpk` deterministic by setting file timestamps to a fixed value, sorting entries, and excluding extra file attributes:
23+
24+
```
25+
cd internal_filesystem/apps/
26+
find com.micropythonos.helloworld -exec touch -t 202501010000.00 {} \;
27+
(find com.micropythonos.helloworld -type d; find com.micropythonos.helloworld -type f) | sort | TZ=CET zip -X -r -0 /tmp/com.micropythonos.helloworld_0.0.2.mpk -@
28+
```
29+
30+
This:
31+
32+
- sorts directories before files
33+
- uses `-0` for stored (uncompressed) entries
34+
- uses `-X` to exclude extra file attributes
35+
- places `com.micropythonos.helloworld/` as the first entry
36+
1837
## AppStore bundling
1938

20-
The apps at https://apps.MicroPythonOS.com are a curated, manually reviewed, vetted collection, often created and maintainced by the MicroPythonOS core team.
39+
The apps at https://apps.MicroPythonOS.com are a curated, manually reviewed, vetted collection, often created and maintained by the MicroPythonOS core team.
2140

22-
These are manually bundled into a [app_index.json](https://github.com/MicroPythonOS/apps/blob/main/app_index.json) using [`scripts/bundleapps.sh`](https://github.com/MicroPythonOS/MicroPythonOS/blob/main/scripts/bundleapps.sh) and then pushed to the [apps repo](https://github.com/MicroPythonOS/apps).
41+
These are bundled into an [`app_index.json`](https://github.com/MicroPythonOS/apps/blob/main/app_index.json) using [`scripts/bundle_apps.sh`](https://github.com/MicroPythonOS/MicroPythonOS/blob/main/scripts/bundle_apps.sh) and then pushed to the [apps repo](https://github.com/MicroPythonOS/apps).

docs/apps/creating-apps.md

Lines changed: 18 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,13 @@ Create the following file and folder structure:
1212

1313
```
1414
com.micropythonos.helloworld/
15-
├── assets/
16-
│   └── hello.py
17-
├── META-INF/
18-
│   └── MANIFEST.JSON
19-
└── res/
20-
└── mipmap-mdpi/
21-
└── icon_64x64.png
15+
├── MANIFEST.JSON
16+
├── icon_64x64.png
17+
└── hello.py
2218
```
2319

20+
This flat layout puts the required `MANIFEST.JSON` and app icon at the top level, alongside your Python files. Subfolders are still allowed if you want to organize larger apps, but **each directory costs about 8 KiB of storage in LittleFS**, so use them sparingly on device.
21+
2422
## App code
2523

2624
In `hello.py`, put:
@@ -45,18 +43,18 @@ The code above creates a new screen, adds a label, sets the label text, centers
4543

4644
In `MANIFEST.JSON`, put:
4745

48-
```
46+
```json
4947
{
50-
"name": "HelloWorld",
51-
"publisher": "MicroPythonOS",
52-
"short_description": "Minimal app",
53-
"long_description": "Demonstrates the simplest app.",
54-
"fullname": "com.micropythonos.helloworld",
55-
"version": "0.0.2",
56-
"category": "development",
57-
"activities": [
48+
"name": "HelloWorld",
49+
"publisher": "MicroPythonOS",
50+
"short_description": "Minimal app",
51+
"long_description": "Demonstrates the simplest app.",
52+
"fullname": "com.micropythonos.helloworld",
53+
"version": "0.0.2",
54+
"category": "development",
55+
"activities": [
5856
{
59-
"entrypoint": "assets/hello.py",
57+
"entrypoint": "hello.py",
6058
"classname": "Hello",
6159
"intent_filters": [
6260
{
@@ -76,7 +74,7 @@ Apps can also declare **services** — background components that run at boot ti
7674
```json
7775
"services": [
7876
{
79-
"entrypoint": "assets/my_boot_service.py",
77+
"entrypoint": "my_boot_service.py",
8078
"classname": "MyBootService",
8179
"intent_filters": [
8280
{
@@ -99,7 +97,7 @@ Services that subscribe to `"boot_completed"` are started automatically during s
9997

10098
## Icon
10199

102-
The icon is a [simple 64x64 pixel PNG image](https://github.com/MicroPythonOS/MicroPythonOS/blob/main/internal_filesystem/builtin/apps/com.micropythonos.launcher/res/mipmap-mdpi/icon_64x64.png), which you can create with any tool, such as GIMP.
100+
The icon is a simple 64x64 pixel PNG image named `icon_64x64.png` in the app root, which you can create with any tool, such as GIMP.
103101

104102
It's recommended to keep it as small as possible by setting compression level to 9 and not storing any metadata such as background color, resolution, creation time, comments, Exif data, XMP data, thumbnail or color profile.
105103

@@ -111,9 +109,7 @@ The app can be installed by copying the top-level folder `com.micropythonos.hell
111109

112110
### On Desktop
113111

114-
You probably already have a clone of the [internal_filesystem](https://github.com/MicroPythonOS/MicroPythonOS/tree/main/internal_filesystem) that you're using to [run MicroPythonOS on desktop](../os-development/running-on-desktop.md).
115-
116-
Just copy or move your the top-level folder `com.micropythonos.helloworld/` (and its contents) to `internal_filesystem/apps/` and you're good to go!
112+
If you are [running MicroPythonOS on desktop](../os-development/running-on-desktop.md) from a source checkout, copy or move the top-level folder `com.micropythonos.helloworld/` (and its contents) to `internal_filesystem/apps/` and you're good to go.
117113

118114
### On ESP32
119115

docs/architecture/filesystem.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ MicroPythonOS uses a structured filesystem to organize apps, data, and resources
55
- **apps/**: Directory for downloaded and installed apps.
66
- **com.micropythonos.helloworld/**: Installation directory for HelloWorld App. See [Creating Apps](../apps/creating-apps.md).
77
- **builtin/**: Read-only filesystem compiled into the OS, mounted at boot by `main.py`.
8-
- **apps/**: See [Built-in Apps](../apps/built-in-apps.md).
8+
- **apps/**: See [Built-in Apps](../apps/built-in-apps.md).
99
- **res/mipmap-mdpi/default_icon_64x64.png**: Default icon for apps without one.
1010
- **lib/**: Libraries and frameworks
1111
- **mpos/**: MicroPythonOS libraries and frameworks

docs/frameworks/app-manager.md

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -121,15 +121,14 @@ Each app must have this directory structure:
121121

122122
```
123123
apps/com.example.myapp/
124-
├── META-INF/
125-
│ └── MANIFEST.JSON # App metadata
126-
├── assets/
127-
│ ├── main.py # Main activity entry point
128-
│ ├── icon.png # App icon
129-
│ └── ... # Other assets
130-
└── ...
124+
├── MANIFEST.JSON # App metadata
125+
├── icon_64x64.png # App icon
126+
├── main.py # Main activity entry point
127+
└── ... # Other files and (optional) subfolders
131128
```
132129

130+
This flat layout keeps `MANIFEST.JSON` and `icon_64x64.png` at the app root. Subfolders are still allowed for larger apps, but remember that **each directory uses roughly 8 KiB of storage in LittleFS**, so use them sparingly on device.
131+
133132
The `MANIFEST.JSON` file contains app metadata:
134133

135134
```json
@@ -138,10 +137,15 @@ The `MANIFEST.JSON` file contains app metadata:
138137
"name": "My App",
139138
"version": "1.0.0",
140139
"description": "A sample app",
141-
"main_launcher_activity": {
142-
"entrypoint": "assets/main.py",
143-
"classname": "Main"
144-
}
140+
"activities": [
141+
{
142+
"entrypoint": "main.py",
143+
"classname": "Main",
144+
"intent_filters": [
145+
{ "action": "main", "category": "launcher" }
146+
]
147+
}
148+
]
145149
}
146150
```
147151

@@ -339,9 +343,9 @@ from mpos import AppManager
339343

340344
# Execute a script file
341345
success = AppManager.execute_script(
342-
script_source="assets/main.py",
346+
script_source="main.py",
343347
classname="Main",
344-
cwd="apps/com.example.myapp/assets/"
348+
cwd="apps/com.example.myapp/"
345349
)
346350
```
347351

@@ -548,7 +552,7 @@ Start an app by fullname.
548552
Execute a Python script with proper environment.
549553

550554
- **Parameters:**
551-
- `script_source` (str): Script path used to derive module name (e.g., `assets/main.py` -> `main`)
555+
- `script_source` (str): Script path used to derive module name (e.g., `main.py` -> `main`)
552556
- `classname` (str): Name of main activity class to instantiate
553557
- `cwd` (str, optional): Working directory to add to sys.path
554558

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
# NotificationManager
2+
3+
`NotificationManager` is a system-wide framework for posting, displaying, and dispatching notifications. It is inspired by Android's notification system, but tailored for resource-constrained devices.
4+
5+
Notifications appear in the top notification bar and in the pull-down drawer. Tapping a notification dispatches its attached `Intent`, which can open an activity or start an app.
6+
7+
## Overview
8+
9+
- Apps post `Notification` objects through `NotificationManager.notify()`.
10+
- The system UI listens for changes and updates the notification bar and drawer automatically.
11+
- Notifications are persisted to storage (with a debounced write) so they survive reboots.
12+
- Notifications can be cancelled individually, in bulk, or automatically when tapped.
13+
14+
## Basic Usage
15+
16+
```python
17+
from mpos import NotificationManager, Notification, Intent
18+
19+
notification = Notification(
20+
notification_id="myapp.event",
21+
icon=lv.SYMBOL.BELL,
22+
title="Event",
23+
text="Something happened!",
24+
intent=Intent(action="main", app_fullname="com.example.myapp"),
25+
auto_cancel=True,
26+
)
27+
28+
NotificationManager.notify(notification)
29+
```
30+
31+
To remove a notification:
32+
33+
```python
34+
NotificationManager.cancel("myapp.event")
35+
```
36+
37+
## Notification Object
38+
39+
`Notification` is a plain data object that describes a single notification.
40+
41+
### Constructor Arguments
42+
43+
| Argument | Type | Description |
44+
|----------|------|-------------|
45+
| `notification_id` | `str` | Unique identifier. Also accepts `uniqueidString` for compatibility. Required. |
46+
| `icon` | `str` or `lv.image_dsc_t` | Icon shown in the bar/drawer. Can be an `lv.SYMBOL.*` string, another string, or an LVGL image descriptor. |
47+
| `title` | `str` | Short title. |
48+
| `text` | `str` | Longer body text. |
49+
| `priority` | `int` | One of `Notification.PRIORITY_MIN`, `PRIORITY_LOW`, `PRIORITY_DEFAULT`, `PRIORITY_HIGH`, `PRIORITY_MAX`. Higher priority notifications are shown first. |
50+
| `intent` | `Intent` | `Intent` to dispatch when the user taps the notification. |
51+
| `auto_cancel` | `bool` | If `True` (default), the notification is cancelled automatically after it is tapped. |
52+
| `app_fullname` | `str` | App that owns the notification. Used as a fallback target if the intent has no explicit target. |
53+
54+
### Priority Levels
55+
56+
```python
57+
Notification.PRIORITY_MIN = -1
58+
Notification.PRIORITY_LOW = 0
59+
Notification.PRIORITY_DEFAULT = 1
60+
Notification.PRIORITY_HIGH = 2
61+
Notification.PRIORITY_MAX = 3
62+
```
63+
64+
The drawer sorts notifications by priority, then by most recent update time.
65+
66+
## NotificationManager API
67+
68+
All methods are class methods. `NotificationManager` initializes itself lazily on first use.
69+
70+
### `notify(notification)`
71+
72+
Post or update a notification.
73+
74+
- If a notification with the same ID already exists, its content is updated in place without a new persistence flash.
75+
- If it is new, it is added, the list is trimmed to `MAX_NOTIFICATIONS` (20), and persistence is scheduled.
76+
77+
```python
78+
NotificationManager.notify(Notification(
79+
notification_id="osupdate.available",
80+
icon=lv.SYMBOL.DOWNLOAD,
81+
title="Update available",
82+
text="MicroPythonOS 1.2.3 is ready to install",
83+
priority=Notification.PRIORITY_HIGH,
84+
))
85+
```
86+
87+
### `cancel(notification_id)`
88+
89+
Remove a single notification. Writes immediately so the notification does not reappear after reboot.
90+
91+
```python
92+
NotificationManager.cancel("osupdate.available")
93+
```
94+
95+
### `cancel_all()`
96+
97+
Remove all notifications.
98+
99+
```python
100+
NotificationManager.cancel_all()
101+
```
102+
103+
### `get_notifications()`
104+
105+
Return a sorted list of active notifications, highest priority first.
106+
107+
```python
108+
for n in NotificationManager.get_notifications():
109+
print(n.title, n.text)
110+
```
111+
112+
### `get_notification(notification_id)`
113+
114+
Return a single notification by ID, or `None` if it doesn't exist.
115+
116+
```python
117+
n = NotificationManager.get_notification("osupdate.available")
118+
```
119+
120+
### `trigger(notification_id_or_object)`
121+
122+
Dispatch the notification's intent. This is what the system calls when the user taps a notification in the drawer.
123+
124+
- If the intent has an explicit `activity_class` or `action`, `ActivityNavigator.startActivity()` is used.
125+
- Otherwise, the owning `app_fullname` is started via `AppManager.start_app()`.
126+
- If `auto_cancel` is enabled and the dispatch succeeds, the notification is cancelled.
127+
128+
```python
129+
NotificationManager.trigger("osupdate.available")
130+
```
131+
132+
### `register_listener(callback, notify_immediately=True)` / `unregister_listener(callback)`
133+
134+
Register a function to be called whenever notifications change. The top menu/drawer uses this to refresh the UI.
135+
136+
```python
137+
def on_notifications_changed():
138+
print("Notifications updated")
139+
140+
NotificationManager.register_listener(on_notifications_changed)
141+
```
142+
143+
## Complete Example
144+
145+
```python
146+
import lvgl as lv
147+
from mpos import Activity, NotificationManager, Notification, Intent
148+
149+
class MyApp(Activity):
150+
151+
def show_notification(self):
152+
intent = Intent(action="main", app_fullname="com.example.myapp")
153+
notification = Notification(
154+
notification_id="com.example.myapp.done",
155+
icon=lv.SYMBOL.OK,
156+
title="Done",
157+
text="The operation finished successfully.",
158+
intent=intent,
159+
auto_cancel=True,
160+
app_fullname="com.example.myapp",
161+
)
162+
NotificationManager.notify(notification)
163+
```
164+
165+
## Best Practices
166+
167+
### Do's
168+
169+
✅ Use a stable, unique `notification_id` so the same event doesn't create duplicate notifications
170+
✅ Set a meaningful `app_fullname` so tapping the notification can fall back to launching your app
171+
✅ Cancel notifications when they are no longer relevant
172+
✅ Use `auto_cancel=True` for one-shot notifications that should disappear after being tapped
173+
174+
### Don'ts
175+
176+
❌ Don't rely on `lv.image_dsc_t` icons surviving a reboot — only string icons are persisted
177+
❌ Don't post large amounts of text; storage and display space are limited
178+
❌ Don't use more than `MAX_NOTIFICATIONS` (20) active notifications
179+
180+
## See Also
181+
182+
- [Service](service.md) — Background services that often post notifications at boot
183+
- [SettingActivity](setting-activity.md) — Per-app settings storage, useful with notification preferences
184+
- [App Lifecycle](../apps/app-lifecycle.md) — Foreground handling when notifications launch your app

0 commit comments

Comments
 (0)