Table of Contents
- 1 Introduction
- 2 What Is MicroPythonOS?
- 3 The Waveshare ESP32-S3-Touch-LCD-2
- 4 Installing MicroPythonOS
- 5 Taking MicroPythonOS for a Spin
- 6 Setting Up a Linux Development Workstation
- 7 Building a MicroPythonOS App: The Countdown Timer
- 8 Deploying the App to the ESP32
- 9 Key Development Notes for MicroPythonOS v0.8.0
- 10 Conclusion
We’re used to operating systems on a microcomputer like the Raspberry Pi, but you usually don’t see them used with microcontrollers. Today, we’ll look at a unique product – MicroPythonOS, an Android-like operating system for the ESP32.
Introduction
Microcontroller projects have traditionally been limited by what you can fit on a tiny chip — bare-bones programs, simple text output, and a complete absence of any real graphical user interface. That world is changing fast. Meet MicroPythonOS: a full, Android-inspired operating system that runs entirely on an ESP32 microcontroller and is written completely in MicroPython.
MicroPythonOS gives your ESP32 project a proper touchscreen interface, an App Store, over-the-air OS updates, a Wi-Fi manager, and a growing library of user-installable apps. If that sounds surprising, it should. This is a genuinely new category of embedded software, and it represents a significant step forward for anyone building maker or IoT projects that need a real user interface.

In this tutorial, we will cover everything from installing MicroPythonOS on real hardware and touring the built-in interface, through to setting up a Linux development workstation and writing and deploying a complete MicroPythonOS app from scratch. By the end, you will have a working Countdown Timer app running on a Waveshare ESP32 touch-screen board.
| IMPORTANT
MicroPythonOS is an actively developed software. The current release at the time of writing is v0.8.0. The API, app structure, and package names may change between versions. If you are working through this tutorial after a new release, always check the official documentation at docs.micropythonos.com first and compare it against the steps shown here before proceeding. |
What Is MicroPythonOS?
MicroPythonOS is a complete operating system for microcontrollers, inspired by the design of Android and iOS. It is built on the MicroPython runtime and uses LVGL (Light and Versatile Graphics Library) for its graphical interface. Unlike traditional embedded firmware — where you write one monolithic program — MicroPythonOS gives you a persistent operating environment that launches apps, manages the display, handles touch input, and keeps itself up to date, all independently of whatever apps you choose to run.
Perhaps the most striking thing about MicroPythonOS is that it is written almost entirely in MicroPython — a language that most people would consider too slow or too high-level for an operating system. The project proves that assumption wrong. Careful use of LVGL’s C-based graphics engine, combined with MicroPython’s event-driven architecture, produces a system that feels genuinely responsive on modest hardware.
Architecture
MicroPythonOS is structured in layers. At the very bottom is the hardware layer — an ESP32 or RP2350 microprocessor. Above that sits the thin OS core, which handles hardware initialisation, display management, touch input, and multitasking. This layer runs continuously and is never replaced when apps are installed. Next comes the LVGL user interface layer, which manages touch events, gestures, widgets, and themes. Above LVGL sits the frameworks layer — this is the mpos library that your apps import, providing services like AudioManager, AppManager, AppearanceManager, BatteryManager, DisplayMetrics, and more. At the very top are the apps themselves: Wi-Fi, Settings, Camera, or apps you build yourself.

The filesystem architecture is equally important. MicroPythonOS uses a LittleFS filesystem on the ESP32’s flash storage. This does not appear as a normal USB drive on your computer — you cannot simply drag and drop files onto the device. Instead, you use the mpremote command-line tool to transfer files over the serial connection.
Running on the Desktop
One of MicroPythonOS’s most useful features for developers is that it can run as a native application on Linux, macOS, and Windows. The desktop version runs the same MicroPython and LVGL stack as the hardware version, which means you can write and test app code on your PC before ever touching the ESP32. The desktop binary reads from the same internal_filesystem/ folder structure, so editing a file and relaunching the desktop instance takes only seconds. We will use this extensively when building our Countdown Timer app.
Built-In Apps
| App | Description |
| Launcher | The home screen. Displays all installed apps as a grid of icon tiles. Supports adaptive layout for different screen widths. |
| WiFi | Connect to Wi-Fi networks. Scans for available access points and saves credentials for automatic reconnection on future boots. |
| AppStore | Browse and install community-contributed apps directly from the MicroPythonOS repository. Alternate app store sources can be configured in the App Store settings. |
| OSUpdate | Check for and install over-the-air operating system updates without reflashing. |
| Settings | Configure display brightness, light/dark theme, time zone, IMU calibration, and auto-start app. Changes take effect immediately. |
| About | Displays build information, chip ID, and MicroPythonOS version details. |
App Store Highlights
The App Store includes a growing collection of community-contributed apps. Some of the more useful ones for makers include:
- Hello World — the minimal reference app demonstrating the basic app structure
- Camera — live camera preview (requires a compatible CSI camera module)
- Image Viewer — browse and display images stored on the device filesystem
- IMU — 3D visualisation of motion sensor data from a connected accelerometer/gyroscope
- Calculator — a touch-screen calculator
- Connect 4, Doom, QuasiBird — games (Doom requires your own DOOM.WAD file)
Hardware Support
MicroPythonOS supports a wide range of peripherals on the ESP32 platform, including touchscreen displays, Wi-Fi and Bluetooth, cameras, motion sensors, GPIO, I2C, SPI, ADC, and IO expanders. The list of officially supported boards is growing quickly. Support for the Raspberry Pi RP2350 is on the development roadmap.
For this tutorial, we are using the Waveshare ESP32-S3-Touch-LCD-2. This is a compact, self-contained development board built around the ESP32-S3 chip, featuring a 2-inch IPS LCD with capacitive touch — making it one of the officially tested and supported MicroPythonOS hardware targets.

| Feature | Detail |
| CPU | ESP32-S3 dual-core Xtensa LX7, up to 240 MHz |
| RAM | 512 KB internal SRAM + 8 MB PSRAM (QSPI) |
| Flash | 16 MB QSPI flash storage |
| Display | 2.0-inch IPS LCD, 320 × 240 pixels, 65K colours |
| Touch | Capacitive touch (CST816S controller) |
| Camera | OV5640 5 MP camera interface (optional, connects via ribbon cable) |
| IMU | 6-axis IMU (built-in, used for orientation detection) |
| Storage | Micro SD card slot |
| Connectivity | Wi-Fi 802.11 b/g/n 2.4 GHz, Bluetooth 5.0 LE |
| USB | USB-C (native ESP32-S3 USB — programming and power) |
| GPIO | Expansion connector with I2C, SPI, UART, and GPIO pins (labelled on board) |
| Power | USB-C (5 V) with built-in LiPo battery connector and charging circuit |
MicroPythonOS includes display driver configuration and touch calibration specifically for this board, so installation works out of the box without any manual driver setup. The 16 MB flash and 8 MB PSRAM provide MicroPythonOS with plenty of space to store apps and handle LVGL’s graphics buffers. MicroPythonOS v0.8.0 also introduced an adaptive launcher layout that adjusts the icon grid for narrower screens, so the home screen looks well-proportioned on the 320×240 display.
| NOTE
Before connecting the board for the first time on Linux, make sure you have added your user account to the dialout group. Without this, Linux will refuse permission to access the serial port. This is covered in the workstation setup section. |
Installing MicroPythonOS
Installing MicroPythonOS on the Waveshare board is remarkably straightforward, thanks to the browser-based web installer. You do not need to install any special software on your computer — everything is handled in your browser using the WebSerial API.
| IMPORTANT
The WebSerial API is only supported in Chromium-based browsers: Google Chrome, Microsoft Edge, and Brave. Firefox and Safari do not support WebSerial. Make sure you are using one of these browsers before starting. |
Step 1 — Put the Board into Bootloader Mode
Before connecting via USB, you must put the board into bootloader mode. To do this: hold down the BOOT key on the back of the board, then plug in the USB-C cable, then release the BOOT key. The board is now in bootloader mode and ready for the installer.

Step 2 — Open the Web Installer
Navigate to https://install.micropythonos.com in your Chromium-based browser. This page always serves the latest stable release. Note the version number shown — at the time of writing this is v0.8.0.
Step 3 — Connect and Install
Scroll down on the installer page and click the Install button. Your browser will display a pop-up asking you to select a serial port. Look for a device labelled USB Single Serial or similar — that is your board. Select it and click Connect.

You will be given the option to erase the device before installation (recommended for a clean install). Click Install. The installer will erase the flash, write the MicroPythonOS firmware, and configure the partition table. This typically takes 60 to 90 seconds. Do not disconnect the board during this process.
| NOTE
On Linux, if no port appears in the browser pop-up, your user account may not have permission to access serial devices. Log out, log back in, and verify you are in the dialout group as described in the workstation setup section. |
Step 4 — First Boot
When installation completes, the board will reboot automatically. After a few seconds, the MicroPythonOS launcher will appear on the screen, showing a grid of app icons. Installation is complete.

| TIP
If the screen remains blank after installation, press the RESET button on the board. If the problem persists, repeat the installation procedure from the beginning. |
Taking MicroPythonOS for a Spin
Once MicroPythonOS is running, you will find a well-polished, touch-driven interface that will feel immediately familiar if you have ever used a smartphone. Let us walk through the main areas.
The Launcher
The home screen — called the Launcher — displays all installed apps as a grid of icon tiles. Each tile shows the app icon (a 64×64 PNG image) and the app name below it. On first boot, you will see a small set of built-in apps: About, App Store, OS Update, Settings, and WiFi.
MicroPythonOS supports two primary navigation gestures:
- Swipe down from the top — opens the control overlay. This panel shows a brightness slider, quick-access buttons for Wi-Fi and Settings, a power-off button, a reset button, and a Launch button that returns you to the main screen from anywhere in the OS.
- Swipe right from the left edge — navigates back to the previous screen. This is especially useful in multi-level menus such as Settings, where you can step back without having to return all the way to the launcher.
Settings
The Settings app provides access to: display brightness and light/dark theme; time zone selection (a drop-down list of locations — set this once you have Wi-Fi connected to get the correct clock); auto-start app configuration; IMU calibration; and a Restart Bootloader option if you ever want to reinstall the OS. Theme and brightness changes take effect immediately.
Setting Up Wi-Fi
Connecting to Wi-Fi is required before you can use the App Store or OS Update. Tap the WiFi app icon on the launcher and follow these steps:
- Tap the WiFi app to open it.
- The app scans for available networks and displays them as a list.
- Tap your network name.
- A custom on-screen keyboard with large touch-friendly buttons will appear. Type your Wi-Fi password.
- Tap Connect. The app will join the network and display a confirmation.
Wi-Fi credentials are saved automatically. The device will reconnect on future boots. Once connected you will see a clock replace the boot timer at the top of the screen, and a Wi-Fi signal strength icon appears near the battery indicator. Go into Settings and set your time zone to get the correct local time.
OS Update
With Wi-Fi active, open the OSUpdate app to check for operating system updates. The app queries the MicroPythonOS update server, displays the current and available versions, and lets you install updates with a single tap. The device will download and apply the update, then reboot.
| NOTE
Always run OS Update before beginning development work. Having the latest version ensures that the APIs and frameworks you write your apps against are current. |
The App Store
The App Store fetches the current catalogue from the MicroPythonOS repository and displays it as a scrollable list. Tap any app to see its description, then tap Install to download it. Installed apps appear in the launcher immediately. The App Store settings let you switch between the default repository and alternative app store sources to explore additional collections.

Setting Up a Linux Development Workstation
This section covers setting up a complete MicroPythonOS development environment on Ubuntu 24.04 LTS. We will install mpremote for file transfer to the ESP32, install the MicroPythonOS desktop binary for local app testing, and install the Thonny IDE as our code editor.
| UBUNTU 24.04 – PACKAGE NAME NOTE
These instructions have been specifically tested and corrected for Ubuntu 24.04 LTS. The official MicroPythonOS documentation lists package names that are incorrect for Ubuntu 24.04. Two critical corrections: (1) librlottie0 does not exist on Ubuntu 24.04 — the correct package name is librlottie0-1, which is in the universe repository. (2) libv4l-0 has been renamed to libv4l-0t64 due to Ubuntu 24.04’s 64-bit time_t ABI transition. Using the old names from the official documentation will produce “unable to locate package” errors. Follow the steps below exactly. |
Part A: Installing mpremote
mpremote is the official MicroPython tool for managing files on a MicroPython device over a serial connection. Because MicroPythonOS uses LittleFS on the ESP32’s flash storage, the device does not appear as a USB drive — mpremote is how you copy files to and from the hardware. We install it using pipx, which isolates Python tools in their own environments without interfering with the system Python installation.
Step 1 — Update the package list
|
1 |
sudo apt update |
Step 2 — Install Python tools and pipx
|
1 |
sudo apt install python3-pip python3-venv pipx -y |
Step 3 — Add pipx binaries to your PATH
|
1 |
pipx ensurepath |
Close your terminal, open a new one to apply the PATH change, then continue.
Step 4 — Install mpremote
|
1 |
pipx install mpremote |
Step 5 — Add your user to the dialout group
Linux restricts access to serial ports to members of the dialout group. Without this, your user account cannot communicate with the ESP32.
|
1 |
sudo usermod -a -G dialout $USER |
Log out and log back in for the group change to take effect, then verify with:
|
1 |
groups |
The word dialout should appear in the list.
Step 6 — Test mpremote
With the Waveshare board connected via USB-C, run:
|
1 |
mpremote connect list |
You should see your board listed — typically as /dev/ttyACM0 or /dev/ttyUSB0 labelled as “Espressif Systems Device”. If the list is empty, confirm the board is powered and that the dialout group change has fully taken effect (it requires a complete log out and log back in, not just a new terminal window).

To verify the REPL connection, run mpremote on its own:
|
1 |
mpremote |
You should get a MicroPython >>> prompt. Type a quick test and press Enter:
|
1 |
print('hello') |
If hello prints back, mpremote is working correctly. Press Ctrl+X to exit.
Part B: Installing the MicroPythonOS Desktop Binary
The MicroPythonOS desktop binary lets you run MicroPythonOS on your Linux PC. It runs the same MicroPython and LVGL stack as the hardware, so you can write and test apps without touching the ESP32 at all. Edit a file in Thonny, save it, relaunch the desktop instance, and your app is running — all in a few seconds.
Step 1 — Enable the universe repository
librlottie0-1 is in Ubuntu’s universe repository. Enable it if it is not already active:
|
1 |
sudo add-apt-repository universe |
|
1 |
sudo apt update |
Step 2 — Install runtime dependencies
Copy and run the following as a single command:
|
1 2 3 4 |
sudo apt-get install -y \ librlottie0-1 libdecor-0-0 libv4l-0t64 \ libxss1 libjack-jackd2-0 libsndio7.0 \ libibus-1.0-5 libxkbcommon0 git |
| UBUNTU 24.04 – PACKAGE NAME NOTE
Use librlottie0-1 (NOT librlottie0) and libv4l-0t64 (NOT libv4l-0). These names changed in Ubuntu 24.04 due to updated library versions and the 64-bit time_t ABI transition. The official MicroPythonOS documentation still lists the old names, which will cause “unable to locate package” errors on Ubuntu 24.04. |
Step 3 — Clone the MicroPythonOS repository
|
1 2 3 4 |
cd ~ git clone --recurse-submodules \ https://github.com/MicroPythonOS/MicroPythonOS.git cd MicroPythonOS/ |
This downloads the full repository, including submodules. It may take a couple of minutes.
Step 4 — Download and install the desktop binary
Visit https://github.com/MicroPythonOS/MicroPythonOS/releases and download the latest Linux release. Currently this is MicroPythonOS_amd64_linux_0.8.0.elf
Create the required build directory and copy the binary into it with the exact filename that the launch script expects:
|
1 2 3 |
mkdir -p ~/MicroPythonOS/lvgl_micropython/build cp ~/Downloads/MicroPythonOS_amd64_linux_0.8.0.elf \ ~/MicroPythonOS/lvgl_micropython/build/lvgl_micropy_unix |
Make the binary executable:
|
1 |
chmod +x ~/MicroPythonOS/lvgl_micropython/build/lvgl_micropy_unix |
| NOTE
The filename lvgl_micropy_unix is fixed and must not be changed. The run_desktop.sh script looks for this exact name. Renaming the file will cause the script to fail. |
Step 5 — Launch the desktop instance
|
1 |
cd ~/MicroPythonOS && ./scripts/run_desktop.sh |
A window will open showing the MicroPythonOS launcher. You can click (simulating touch) to navigate the UI just as you would on the hardware. To exit, close the window or press Ctrl+C in the terminal.

| TIP
The desktop instance reads apps directly from ~/MicroPythonOS/internal_filesystem/apps/. Editing an app file there and re-running ./scripts/run_desktop.sh shows your changes immediately — no copy or transfer step is needed during development. |
Part C: Installing Thonny IDE
Thonny is a beginner-friendly Python IDE that we will use to write our app code. It provides syntax highlighting and a clean file panel without the complexity of a full development environment.
| NOTE
MicroPythonOS locks the REPL on the connected ESP32. This means Thonny’s MicroPython Device mode (where you run code directly on the board from Thonny) will not work as expected with MicroPythonOS. We use Thonny only as a text editor. All file transfers to the ESP32 are done via mpremote from the terminal. |
Step 1 — Install Thonny
|
1 |
sudo apt install thonny -y |
Step 2 — Launch Thonny
|
1 |
thonny & |
No special configuration is required. Use Thonny to open, edit, and save Python (.py) and JSON files in your app directories. The file panel on the left makes it easy to navigate the app folder structure.
Building a MicroPythonOS App: The Countdown Timer
Now for the main event. We are going to build a complete MicroPythonOS app from scratch: a Countdown Timer. This app demonstrates all the fundamental app-building concepts — directory structure, manifest file, LVGL UI construction, the Activity lifecycle, and timer callbacks — while remaining simple enough to understand in full.
Our Countdown Timer will:
- Count down from 10 to 0, displaying the current number in a large font in the centre of the screen.
- Show Start and Reset buttons at the bottom of the screen.
- Display a status message that changes between “Press Start”, “Running…”, and “Time’s Up!”.
- Pause the countdown correctly when the app is sent to the background.
- Resume correctly when the app returns to the foreground.
Understanding App Structure
Every MicroPythonOS app follows a specific directory layout. Apps are named using reverse DNS notation — the same convention used by Android and iOS. Our app identifier is com.dronebotworkshop.countdown. Obviously, you should substitute your own name here!

| NOTE
The app folder name must exactly match the fullname field in MANIFEST.JSON. MicroPythonOS uses this to locate and register your app. A mismatch between the folder name and the fullname will cause the app not to appear in the launcher. |
com.dronebotworkshop.countdown/
├── assets/
│ └── countdown.py ← Python source file (Activity class)
├── META-INF/
│ └── MANIFEST.JSON ← App metadata and OS registration
└── res/
└── mipmap-mdpi/
└── icon_64x64.png ← App icon (64×64 pixels, PNG)
This structure is not optional. MicroPythonOS expects exactly this layout. Let us create each piece.
Step 1: Create the Directory Structure
Open a terminal and run these three commands to create the folder structure inside the MicroPythonOS apps directory:
|
1 2 3 |
mkdir -p ~/MicroPythonOS/internal_filesystem/apps/com.dronebotworkshop.countdown/assets mkdir -p ~/MicroPythonOS/internal_filesystem/apps/com.dronebotworkshop.countdown/META-INF mkdir -p ~/MicroPythonOS/internal_filesystem/apps/com.dronebotworkshop.countdown/res/mipmap-mdpi |
Step 2: Create the MANIFEST.JSON File
Create the following file at:
~/MicroPythonOS/internal_filesystem/apps/com.dronebotworkshop.countdown/META-INF/MANIFEST.JSON
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
{ "name": "Countdown", "publisher": "DroneBot Workshop", "fullname": "com.dronebotworkshop.countdown", "version": "1.0.0", "category": "tools", "activities": [ { "entrypoint": "assets/countdown.py", "classname": "CountdownTimer", "intent_filters": [{"action": "main", "category": "launcher"}] } ] } |
Manifest Fields Explained
| Field | Description |
| name | The display name shown under the icon in the launcher. |
| publisher | Your name or organisation name. |
| fullname | Reverse DNS identifier. Must exactly match the app folder name. |
| version | Semantic version number of your app. |
| category | Used by the App Store. Common values: tools, games, utilities. |
| entrypoint | Path to the Python file containing the Activity class (relative to the app folder). |
| classname | The name of the Activity class inside the entry point file. |
| intent_filters | Always set action to “main” and category to “launcher” to make the app appear on the home screen. |
Step 3: Understanding the Activity Lifecycle
The fundamental building block of any MicroPythonOS app is the Activity class. It represents a single screen in your app. MicroPythonOS calls specific methods on your Activity at key moments in the app’s life — these are called lifecycle callbacks.
| Callback | When it is called, what to do |
| onCreate() | Called once when the app first launches. Build your UI here, create LVGL widgets, initialise variables. |
| onResume(screen) | Called when the app comes to the foreground. Start timers, refresh data. |
| onPause(screen) | Called when the app moves to the background. Stop timers, save state. |
| setContentView(screen) | Not a callback — you must call this at the end of onCreate() to tell MicroPythonOS to display your screen. Without it, nothing will appear. |
Step 4: Write the App Code
Create the following file at:
~/MicroPythonOS/internal_filesystem/apps/com.dronebotworkshop.countdown/assets/countdown.py
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 |
import lvgl as lv from mpos import Activity class CountdownTimer(Activity): COUNTDOWN_START = 10 # Change this to adjust the timer duration def onCreate(self): self._count = self.COUNTDOWN_START self._timer = None self._running = False self.screen = lv.obj() self.screen.set_style_bg_color(lv.color_hex(0x1A1A2E), 0) self.title = lv.label(self.screen) self.title.set_text('Countdown Timer') self.title.align(lv.ALIGN.TOP_MID, 0, 10) self.title.set_style_text_color(lv.color_hex(0xFFFFFF), 0) self.count_label = lv.label(self.screen) self.count_label.set_text(str(self._count)) self.count_label.align(lv.ALIGN.CENTER, 0, -20) self.count_label.set_style_text_font(lv.font_montserrat_48, 0) self.count_label.set_style_text_color(lv.color_hex(0x1A6B9A), 0) self.status_label = lv.label(self.screen) self.status_label.set_text('Press Start') self.status_label.align(lv.ALIGN.CENTER, 0, 40) self.status_label.set_style_text_color(lv.color_hex(0xAAAAAA), 0) self.start_btn = lv.button(self.screen) self.start_btn.align(lv.ALIGN.BOTTOM_LEFT, 10, -10) self.start_btn.set_size(90, 40) self.start_btn.add_event_cb(self._on_start, lv.EVENT.CLICKED, None) start_lbl = lv.label(self.start_btn) start_lbl.set_text('Start') start_lbl.center() self.reset_btn = lv.button(self.screen) self.reset_btn.align(lv.ALIGN.BOTTOM_RIGHT, -10, -10) self.reset_btn.set_size(90, 40) self.reset_btn.add_event_cb(self._on_reset, lv.EVENT.CLICKED, None) reset_lbl = lv.label(self.reset_btn) reset_lbl.set_text('Reset') reset_lbl.center() self.setContentView(self.screen) def onResume(self, screen): super().onResume(screen) if self._running: self._start_timer() def onPause(self, screen): super().onPause(screen) if self._timer is not None: self._timer.delete() self._timer = None def _start_timer(self): self._timer = lv.timer_create(self._tick, 1000, None) def _tick(self, timer): self._count -= 1 self.count_label.set_text(str(self._count)) if self._count <= 0: self._running = False self._timer.delete() self._timer = None self.count_label.set_text('0') self.status_label.set_text("Time's Up!") self.status_label.set_style_text_color(lv.color_hex(0xFF4444), 0) def _on_start(self, event): if not self._running and self._count > 0: self._running = True self.status_label.set_text('Running...') self.status_label.set_style_text_color(lv.color_hex(0x44FF44), 0) self._start_timer() def _on_reset(self, event): if self._timer is not None: self._timer.delete() self._timer = None self._running = False self._count = self.COUNTDOWN_START self.count_label.set_text(str(self._count)) self.status_label.set_text('Press Start') self.status_label.set_style_text_color(lv.color_hex(0xAAAAAA), 0) |
Code Walkthrough
The code is organised into several key sections:
- Imports: import lvgl as lv brings in the LVGL graphics library. from mpos import Activity imports the Activity base class that your app must extend.
- Class constant: COUNTDOWN_START = 10 sets the starting value. Change this one line to adjust the timer duration throughout the entire app.
- onCreate(): Builds the entire UI — dark background, white title label, large blue countdown number (using font_montserrat_48), grey status label, and Start and Reset buttons. The setContentView(self.screen) call at the end is mandatory; without it, nothing appears.
- onResume() / onPause(): Together these handle the app lifecycle correctly. When the user leaves the app and returns, the timer restarts exactly where it left off. When the app moves to the background, the timer is paused cleanly by deleting the lv.timer object.
- _tick(): Called by the LVGL timer every 1000 ms. Decrements the count, updates the label, and stops the timer when zero is reached, changing the status to “Time’s Up!” in red.
- lv.button vs lv.btn: MicroPythonOS v0.8.0 uses LVGL v9, which renamed lv.btn() to lv.button(). Always use lv.button() in new code.
Step 5: Add the App Icon
Create or source a 64×64 pixel PNG icon for your app and save it here:
~/MicroPythonOS/internal_filesystem/apps/com.dronebotworkshop.countdown/res/mipmap-mdpi/icon_64x64.png
Icon requirements:
- Exactly 64×64 pixels — no exceptions.
- PNG format.
- Keep the maximum file size between 3 and 10 KB: use maximum PNG compression and strip all metadata. A smaller file transfers faster and uses less of the ESP32’s limited flash storage.
| TIP
You can create a simple icon quickly in GIMP, Inkscape, or any image editor. There are also many free icon libraries online with PNG exports in the right size. Even a plain coloured square with a letter on it works as a placeholder while you develop. |
Step 6: Test on the Desktop
With all three files in place, launch the desktop instance:
|
1 |
cd ~/MicroPythonOS && ./scripts/run_desktop.sh |
Your Countdown Timer icon should appear in the launcher. Click it to launch the app. Click Start to begin the countdown, watch the number decrease by 1 each second, and confirm that the “Time’s Up!” message appears at zero. Click Reset to return to the start.

| TIP
Most common errors at this stage: AttributeError on “btn” — change lv.btn() to lv.button(). SyntaxError on a line with an apostrophe — retype the string directly in Thonny. App icon does not appear in the launcher — check that intent_filters is present in MANIFEST.JSON and that fullname exactly matches the folder name. |
Deploying the App to the ESP32
Once the app works correctly on the desktop, it is time to deploy it to the physical Waveshare board. MicroPythonOS uses mpremote for all file transfers.
Step 1 — Connect the Board
Connect via USB-C and confirm mpremote can see it:
|
1 |
mpremote connect list |
|
1 |
cd ~/MicroPythonOS/internal_filesystem/apps/ |
Step 3 — Copy the App to the Device
Use mpremote fs cp -r to recursively copy the app folder. The colon prefix in the destination path (:/apps/) tells mpremote this is a path on the device, not on your local machine:
|
1 |
mpremote fs cp -r com.dronebotworkshop.countdown/ :/apps/ |
You will see a list of files being transferred as mpremote copies each one. This typically takes 10 to 20 seconds.
Step 4 — Reset the Board
|
1 |
mpremote reset |
The board will reboot, and the MicroPythonOS launcher will reload. Your Countdown Timer icon should appear. Tap it to launch the app and confirm everything works on the hardware exactly as it did on the desktop.

Useful mpremote Commands
| Command | Action |
| mpremote | Connect and open the MicroPython REPL prompt |
| mpremote connect list | List available serial devices |
| mpremote fs ls :/apps/ | List all installed apps on the device |
| mpremote fs ls / | List the root of the device filesystem |
| mpremote fs cp -r myapp/ :/apps/ | Copy an app folder to the device |
| mpremote fs rm -r :/apps/com.example.app/ | Remove an installed app from the device |
| mpremote run myscript.py | Run a local script on the device without copying it |
| mpremote reset | Soft-reset the device (reloads the OS) |
| NOTE
mpremote auto-detects the port when only one serial device is connected. If you have multiple devices plugged in, add –device /dev/ttyACM0 (or the correct port number) after mpremote in each command. The colon prefix (:) in paths like :/apps/ tells mpremote the path is on the remote device, not on your local machine. Without the colon, mpremote interprets the path as a local filesystem path. |
Key Development Notes for MicroPythonOS v0.8.0
| IMPORTANT
LVGL v9 Breaking Change: MicroPythonOS v0.8.0 uses LVGL v9, which renamed several widgets. The most common change that will break existing code or examples written for LVGL v8: lv.btn() has been renamed to lv.button(). If you copy code from older MicroPythonOS tutorials or examples and see an AttributeError mentioning “btn”, this is the cause. Always use lv.button() in new code. |
| IMPORTANT
Smart Quote Pitfall: Copying code from Word documents or PDFs introduces Unicode curly apostrophes (U+2019) that MicroPython’s parser cannot handle without an encoding declaration, causing a SyntaxError on lines containing apostrophes. Always type code directly in Thonny rather than pasting from formatted documents. |
| NOTE
Pre-Recording Setup Tip: If you ever need to uninstall and reinstall MicroPythonOS to record a fresh installation demo, only remove the cloned MicroPythonOS repository folder. Leave the runtime apt libraries, mpremote, pipx, and dialout group membership in place. Re-installing packages that are already present is unnecessary and risks breaking the serial connection. |
Conclusion
MicroPythonOS is a genuinely exciting development in the maker and IoT space. It brings a polished, Android-inspired operating system to a microcontroller that costs just a few dollars, and it makes that OS fully programmable in pure Python — a language accessible to beginners and experienced developers alike.
Starting from a bare board, you have installed MicroPythonOS via the web installer, toured the built-in interface, set up a full Linux development environment with the corrected Ubuntu 24.04 package names, and built and deployed a working app from scratch. The Countdown Timer demonstrates every concept you need to build more ambitious apps: the Activity lifecycle, LVGL widget construction, timer management, and the full app packaging and deployment workflow.
This is a fun product with many practical uses. Remember, you can set an app to start immediately when MicroPythonOS boots up. Using that feature would allow you to rapidly build small applications without having to worry about networking, display, or camera interfaces – the OS takes care of that for you.
Hope you enjoyed the article and video!
Parts List
Here are some components that you might need to complete the experiments in this article. Please note that some of these links may be affiliate links, and the DroneBot Workshop may receive a commission on your purchases. This does not increase the cost to you and is a method of supporting this ad-free website.
Waveshare ESP32-S3 2-Inch Touchscreen (No Camera) Amazon
Waveshare ESP32-S3 2-Inch Touchscreen (With Camera) Amazon
Resources
Code & Cheat Sheet – The code for the App and the Cheat Sheet.
PDF Version – A PDF version of this article.
MicroPythonOS Installer – Web-based installer for MicroPythonOS
MicroPythonOS Documentation – Documentation for MicroPythonOS








Is the microPython code available ? The code and cheat sheet link is greyed out. Thank you.
So sorry about that – had two drafts going at the same time and accidentally published the wrong one! The code, cheat sheet and PDF are now available. Refresh your browser if the links still appear inactive.
./scripts/run_desktop.sh
gives an error:
File “main.py”, line 31, in <module>
File “lib/mpos/__init__.py”, line 22, in <module>
File “lib/mpos/webserver/__init__.py”, line 3, in <module>
File “lib/mpos/webserver/webrepl_http.py”, line 5, in <module>
ImportError: no module named ‘_webrepl’
I did edit files: webrepl.py and webrepl_http.py commenting out references to webrepl….lines 14 and 5 respectively.
Now it runs but the small window seems difficult to use.
This is a know bug, not all Linux builds have _webrepl. Your solution is valid, I think if you just edit webrepl_http.py you’ll be fine. If you want to get fancier (so it works if/when _webrepl becomes available) you can try this:
try:
import _webrepl
except ImportError:
_webrepl = None
I wonder if this will work on my CYD’s.
Check out their Telegram community – a few contributors are checking it out with the CYD right now. BTW, I have a few CYD’s on the way (there seems to be a variety of them) and will be doing an article and video on the CYD very soon.