diff --git a/README.md b/README.md
new file mode 100644
index 00000000..c0ecc504
--- /dev/null
+++ b/README.md
@@ -0,0 +1,44 @@
+PiggyOS
+=======
+
+This is an operating system for microcontrollers like the ESP32.
+
+It's written entirely in MicroPython, including device drivers, interrupt handlers, boot code, multitasking, display handling.
+
+The architecure is inspired by the Android operating system for smartphones:
+- 'thin' operating system with facilities for apps
+- 'everything is an app' idea
+- making it as simple as possible for developers to build new apps
+
+## Apps
+
+The operating system comes with a few apps built-in that are necessary to bootstrap:
+- launcher: to be able to start apps
+- wificonf: to be able to connect to the wifi
+- appstore: to be able to download and install new apps
+
+Furthermore, these apps are also built-in for convenience:
+- osupdate: to download and install operating system updates
+- camera: to take pictures and videos
+- imutest: to test the Inertial Measurement Unit (accelerometer)
+
+## Supported hardware
+
+- https://www.waveshare.com/wiki/ESP32-S3-Touch-LCD-2
+
+## Architecture
+
+- boot.py: initializes the hardware
+- main.py: initializes the User Interface, contains helper functions for apps, and starts the launcher app
+
+## Filesystem layout:
+
+- /apps: new apps are downloaded and installed here
+- /apps/com.example.app1: example app1 installation directory
+- /apps/com.example.app1/MANIFEST.MF: info about app1 such as Name, Start-Script
+- /apps/com.example.app1/mipmap-mdpi: medium dpi images for app1
+- /apps/com.example.app1/mipmap-mdpi/icon_64x64.bin: icon for app1 (64x64 pixels)
+- /builtin/: read-only filesystem that's compiled in and mounted at boot by main.py
+- /builtin/apps: apps that are builtin and necessary for minimal facilities (launcher, wificonf, appstore etc)
+- /builtin/res/mipmap-mdpi/default_icon_64x64.bin: default icon for apps that don't have one
+
diff --git a/files_to_scripts.py b/draft_code/files_to_scripts.py
similarity index 100%
rename from files_to_scripts.py
rename to draft_code/files_to_scripts.py
diff --git a/draft_code/include_in_build.py b/draft_code/include_in_build.py
new file mode 100644
index 00000000..bbb0fbb2
--- /dev/null
+++ b/draft_code/include_in_build.py
@@ -0,0 +1,2 @@
+print("This script will be included in the build.")
+print("You can then run it with: import include_in_build")
diff --git a/draft_code/microdot_webserver.py b/draft_code/microdot_webserver.py
new file mode 100644
index 00000000..3a030501
--- /dev/null
+++ b/draft_code/microdot_webserver.py
@@ -0,0 +1,114 @@
+
+
+
+#import mip
+#mip.install('github:miguelgrinberg/microdot/src/microdot/microdot.py')
+# http://192.168.1.122/upload
+# http://192.168.1.122/files//
+
+
+from microdot import Microdot, Response
+import os
+
+
+# HTML template for the upload form
+UPLOAD_FORM = '''
+
+
+
File Manager
+
+ File Manager
+ View Files
+ Upload File
+
+
+
+'''
+
+app = Microdot()
+
+@app.route('/')
+async def index(request):
+ return 'Files'
+
+
+@app.route('/files/')
+async def list_files(req, path=''):
+ try:
+ # Sanitize path to prevent directory traversal
+ path = path.strip('/')
+ if '..' in path:
+ return Response('Invalid path', status_code=400)
+ # Get directory contents
+ full_path = '/' + path
+ files = os.listdir(full_path) if path else os.listdir('/')
+ html = 'File Manager
'
+ for f in files:
+ link_path = f'{path}/{f}' if path else f
+ html += f'- {f} | Download
'
+ html += '
'
+ return Response(html, headers={'Content-Type': 'text/html'})
+ except OSError as e:
+ return Response(f'Error: {e}', status_code=500)
+
+
+@app.route('/download/')
+async def download_file(req, path):
+ try:
+ full_path = '/' + path.strip('/')
+ with open(full_path, 'rb') as f:
+ content = f.read() # Read in chunks for large files
+ return Response(content, headers={
+ 'Content-Type': 'application/octet-stream',
+ 'Content-Disposition': f'attachment; filename="{path.split("/")[-1]}"'
+ })
+ except OSError as e:
+ return Response(f'Error: {e}', status_code=404)
+
+
+
+@app.route('/upload', methods=['GET'])
+async def upload_form(req):
+ return Response(UPLOAD_FORM, headers={'Content-Type': 'text/html'})
+
+
+
+@app.route('/upload', methods=['POST'])
+async def upload_file(req):
+ try:
+ # Check if form data contains a file
+ if 'file' not in req.form or not req.files['file']['filename']:
+ return Response('No file selected', status_code=400)
+ # Get file details
+ filename = req.files['file']['filename']
+ file_content = req.files['file']['body']
+ # Sanitize filename to prevent path traversal
+ filename = filename.split('/')[-1].split('\\')[-1]
+ if not filename:
+ return Response('Invalid filename', status_code=400)
+ # Save file to filesystem
+ file_path = f'/{filename}'
+ with open(file_path, 'wb') as f:
+ f.write(file_content) # Write in one go for small files
+ # Free memory
+ gc.collect()
+ # Redirect to file listing
+ return Response(status_code=302, headers={'Location': '/files'})
+ except OSError as e:
+ return Response(f'Error saving file: {e}', status_code=500)
+ except MemoryError:
+ return Response('File too large for available memory', status_code=507)
+
+
+
+def startit():
+ app.run(port=80)
+ # http://192.168.1.115:5000
+
+
+import _thread
+_thread.start_new_thread(startit, ())
+
diff --git a/draft_code/qmi8658_reworked.py b/draft_code/qmi8658_reworked.py
new file mode 100644
index 00000000..e9e8fae8
--- /dev/null
+++ b/draft_code/qmi8658_reworked.py
@@ -0,0 +1,142 @@
+import struct
+import time
+
+from machine import I2C
+
+
+# Sensor constants
+_QMI8685_PARTID = const(0x05)
+_REG_PARTID = const(0x00)
+_REG_REVISION = const(0x01)
+
+_REG_CTRL1 = const(0x02) # Serial interface and sensor enable
+_REG_CTRL2 = const(0x03) # Accelerometer settings
+_REG_CTRL3 = const(0x04) # Gyroscope settings
+_REG_CTRL4 = const(0x05) # Magnetomer settings (support not implemented in this driver yet)
+_REG_CTRL5 = const(0x06) # Sensor data processing settings
+_REG_CTRL6 = const(0x07) # Attitude Engine ODR and Motion on Demand
+_REG_CTRL7 = const(0x08) # Enable Sensors and Configure Data Reads
+
+_REG_TEMP = const(0x33) # Temperature sensor.
+
+_REG_AX_L = const(0x35) # Read accelerometer
+_REG_AX_H = const(0x36)
+_REG_AY_L = const(0x37)
+_REG_AY_H = const(0x38)
+_REG_AZ_L = const(0x39)
+_REG_AZ_H = const(0x3A)
+
+_REG_GX_L = const(0x3B) # read gyro
+_REG_GX_H = const(0x3C)
+_REG_GY_L = const(0x3D)
+_REG_GY_H = const(0x3E)
+_REG_GZ_L = const(0x3F)
+_REG_GZ_H = const(0x40)
+
+_QMI8658_I2CADDR_DEFAULT = const(0X6B)
+
+
+_ACCELSCALE_RANGE_2G = const(0b00)
+_ACCELSCALE_RANGE_4G = const(0b01)
+_ACCELSCALE_RANGE_8G = const(0b10)
+_ACCELSCALE_RANGE_16G = const(0b11)
+
+_GYROSCALE_RANGE_16DPS = const(0b000)
+_GYROSCALE_RANGE_32DPS = const(0b001)
+_GYROSCALE_RANGE_64DPS = const(0b010)
+_GYROSCALE_RANGE_128DPS = const(0b011)
+_GYROSCALE_RANGE_256DPS = const(0b100)
+_GYROSCALE_RANGE_512DPS = const(0b101)
+_GYROSCALE_RANGE_1024DPS = const(0b110)
+_GYROSCALE_RANGE_2048DPS = const(0b111)
+
+_ODR_8000HZ = const(0b0000)
+_ODR_4000HZ = const(0b0001)
+_ODR_2000HZ = const(0b0010)
+_ODR_1000HZ = const(0b0011)
+_ODR_500HZ = const(0b0100)
+_ODR_250HZ = const(0b0101)
+_ODR_125HZ = const(0b0110)
+_ODR_62_5HZ = const(0b0111)
+
+
+class QMI8658:
+ global _QMI8658_I2CADDR_DEFAULT
+ def __init__(self,i2c_bus: I2C,address: int = _QMI8658_I2CADDR_DEFAULT,accel_scale: int = _ACCELSCALE_RANGE_8G,gyro_scale: int = _GYROSCALE_RANGE_256DPS):
+ self.i2c = i2c_bus
+ self.address = address
+ # Verify sensor part ID
+ if self._read_u8(_REG_PARTID) != _QMI8685_PARTID:
+ raise AttributeError("Cannot find a QMI8658")
+ # Setup initial configuration
+ self._configure_sensor(accel_scale, gyro_scale)
+ # Configure scales/divisors for the driver
+ self.acc_scale_divisor = {
+ _ACCELSCALE_RANGE_2G: 1 << 14,
+ _ACCELSCALE_RANGE_4G: 1 << 13,
+ _ACCELSCALE_RANGE_8G: 1 << 12,
+ _ACCELSCALE_RANGE_16G: 1 << 11,
+ }[accel_scale]
+ self.gyro_scale_divisor = {
+ _GYROSCALE_RANGE_16DPS: 2048,
+ _GYROSCALE_RANGE_32DPS: 1024,
+ _GYROSCALE_RANGE_64DPS: 512,
+ _GYROSCALE_RANGE_128DPS: 256,
+ _GYROSCALE_RANGE_256DPS: 128,
+ _GYROSCALE_RANGE_512DPS: 64,
+ _GYROSCALE_RANGE_1024DPS: 32,
+ _GYROSCALE_RANGE_2048DPS: 16,
+ }[gyro_scale]
+
+
+ def _configure_sensor(self, accel_scale: int, gyro_scale: int):
+ # Initialize accelerometer and gyroscope settings
+ self._write_u8(_REG_CTRL1, 0x60) # Set SPI auto increment and big endian (Ctrl 1)
+ self._write_u8(_REG_CTRL2, (accel_scale << 4) | _ODR_1000HZ) # Accel Config
+ self._write_u8(_REG_CTRL3, (gyro_scale << 4) | _ODR_1000HZ) # Gyro Config
+ self._write_u8(_REG_CTRL5, 0x01) # Low-pass filter enable
+ self._write_u8(_REG_CTRL7, 0x03) # Enable accel and gyro
+ time.sleep_ms(100)
+
+
+ # Helper functions for register operations
+ def _read_u8(self, reg:int) -> int:
+ return self.i2c.readfrom_mem(self.address, reg, 1)[0]
+
+ def _read_xyz(self, reg:int) -> tuple[int, int, int]:
+ data = self.i2c.readfrom_mem(self.address, reg, 6)
+ return struct.unpack(' float:
+ """Get the device temperature."""
+ temp_raw = self._read_u8(_REG_TEMP)
+ return temp_raw / 256
+
+ @property
+ def acceleration(self) -> tuple[float, float, float]:
+ """Get current acceleration reading."""
+ raw_accel = self._read_xyz(_REG_AX_L)
+ return tuple(val / self.acc_scale_divisor for val in raw_accel)
+
+ @property
+ def gyro(self) -> tuple[float, float, float]:
+ """Get current gyroscope reading."""
+ raw_gyro = self._read_xyz(_REG_GX_L)
+ return tuple(val / self.gyro_scale_divisor for val in raw_gyro)
+
+
+import machine
+sensor = QMI8658(I2C(0, sda=machine.Pin(48), scl=machine.Pin(47)))
+while True:
+ print(f"""
+QMI8685
+{sensor.temperature=}
+{sensor.acceleration=}
+{sensor.gyro=}
+""")
+ time.sleep(1)
diff --git a/draft_code/saved_functions.py b/draft_code/saved_functions.py
new file mode 100644
index 00000000..36eaf0e0
--- /dev/null
+++ b/draft_code/saved_functions.py
@@ -0,0 +1,546 @@
+
+
+
+import network
+import time
+wlan = network.WLAN(network.STA_IF)
+wlan.active(True)
+wlan.connect("SSIDHERE", "PASSWORDHERE")
+print("Connecting to Wi-Fi...")
+time.sleep(5)
+start_app("/apps/com.example.appstore")
+
+
+
+
+import mip
+mip.install('github:echo-lalia/qmi8658-micropython')
+# remove multi line comments
+
+from machine import Pin, I2C
+from qmi8658 import QMI8658
+import time
+import machine
+sensor = QMI8658(I2C(0, sda=machine.Pin(48), scl=machine.Pin(47)))
+print(f"""
+QMI8685
+{sensor.temperature=}
+{sensor.acceleration=}
+{sensor.gyro=}
+""")
+
+
+
+wifi_icon = lv.label(lv.screen_active())
+wifi_icon.set_text("Test label")
+wifi_icon.align(lv.ALIGN.CENTER, 0, 0)
+wifi_icon.set_style_text_color(lv.color_hex(0x0000FF), 0)
+
+
+
+
+# This file is executed on every boot (including wake-boot from deepsleep)
+#import esp
+#esp.osdebug(None)
+#import webrepl
+#webrepl.start()
+
+
+
+# Fetch Bitcoin block height from mempool.space
+def get_block_height():
+ try:
+ response = urequests.get("https://mempool.space/api/blocks/tip/height")
+ if response.status_code == 200:
+ height = response.text.strip() # Returns plain text (e.g., "853123")
+ response.close()
+ return height
+ else:
+ response.close()
+ return "Error: HTTP " + str(response.status_code)
+ except Exception as e:
+ return "Error: " + str(e)
+
+def show_block_height():
+ # Create a label for block height
+ label = lv.label(scr)
+ label.set_text("Bitcoin Block Height: Fetching...")
+ label.set_style_text_color(lv.color_make(0, 255, 0), 0) # Green text
+ label.set_style_text_font(lv.font_montserrat_16, 0) # Larger font (if available)
+ label.align(lv.ALIGN.TOP_LEFT, 10, 200)
+ #label.center()
+
+ # Style for label background
+ style = lv.style_t()
+ style.init()
+ style.set_bg_color(lv.palette_main(lv.PALETTE.DARK)) # Dark background
+ style.set_border_width(2)
+ style.set_border_color(lv.color_make(255, 255, 255)) # White border
+ style.set_pad_all(10)
+ style.set_radius(10)
+ label.add_style(style, 0)
+
+ height = get_block_height()
+ label.set_text(f"Block Height: {height}")
+
+
+
+
+
+
+
+
+# Create file explorer widget
+file_explorer = lv.file_explorer(subwindow)
+file_explorer.set_root_path("/")
+file_explorer.explorer_open_dir('/')
+file_explorer.set_size(210, 210)
+file_explorer.set_mode(lv.FILE_EXPLORER.MODE.DEFAULT) # Default browsing mode
+file_explorer.set_sort(lv.FILE_EXPLORER.SORT.NAME_ASC) # Sort by name, ascending
+file_explorer.align(lv.ALIGN.CENTER, 0, 0)
+def file_explorer_event_cb(e):
+ code = e.get_code()
+ obj = e.get_target_obj()
+ if code == lv.EVENT.VALUE_CHANGED:
+ #selected_path = obj.get_selected_file_name()
+ selected_path = file_explorer.explorer_get_selected_file_name
+ print("Selected:", selected_path)
+ if obj.is_selected_dir():
+ print("This is a directory")
+ else:
+ print("This is a file")
+
+
+# Attach event callback
+file_explorer.add_event_cb(file_explorer_event_cb, lv.EVENT.VALUE_CHANGED, None)
+
+
+
+
+
+#show_block_height()
+
+# Connect to Wi-Fi and fetch block height
+#if connect_wifi():
+#else:
+# label.set_text("Block Height: Wi-Fi Error")
+
+
+import os
+print(os.listdir('/'))
+try:
+ with open('/boot.py', 'r') as file:
+ print("Contents of /boot.py:")
+ print("-" * 20)
+ for line in file:
+ print(line.rstrip()) # Remove trailing newlines for clean output
+except OSError as e:
+ print("Error reading /boot.py:", e)
+
+#with open('/block_height.txt', 'w') as f:
+# f.write('853123')
+
+
+
+
+
+
+# ffmpeg isn't compiled in...
+# Create video widget
+subwindow.clean()
+video = lv.video(subwindow)
+video.set_size(320, 200)
+video.center()
+
+# Open and play video using LVGL's FFmpeg backend
+video_path = "/video/video_320x180.avi"
+video.set_src(f"file://{video_path}")
+video.set_play_mode(lv.VIDEO_PLAY_MODE.PLAY) # Start playback
+video.play()
+
+
+script_globals = {
+ 'lv': lv,
+ #'subwindow': lvgl_obj,
+}
+
+with open("/launcher.py", 'r') as f:
+ script_source = f.read()
+
+
+exec(script_source, script_globals)
+
+
+
+
+
+
+
+# Debug file system
+image_path = "/test.jpg"
+try:
+ print("Checking file system...")
+ print(f"Apps dir: {uos.listdir('/apps')}")
+ print(f"App1 dir: {uos.listdir('/apps/com.example.app1')}")
+ print(f"Res dir: {uos.listdir('/apps/com.example.app1/res')}")
+ print(f"Mipmap dir: {uos.listdir('/apps/com.example.app1/res/mipmap-mdpi')}")
+ print(f"File exists: {image_path in uos.listdir('/apps/com.example.app1/res/mipmap-mdpi')}")
+except Exception as e:
+ print(f"File system error: {e}")
+
+# Load and display the image
+print("Creating image widget...")
+image = lv.image(lv.screen_active())
+try:
+ print(f"Loading image: {image_path}")
+ image.set_src(image_path)
+ image.align(lv.ALIGN.CENTER, 0, 0)
+ print("Image loaded and aligned")
+except Exception as e:
+ print(f"Image load error: {e}")
+
+# Debug screen and widget
+print(f"Screen active: {lv.screen_active()}")
+print(f"Image widget: {image}")
+
+
+
+
+
+
+
+# Debug display
+#print(f"Display registered: {lv.disp}")
+#print(f"Display resolution: {lv.display_get_horizontal_resolution(lv.disp)}x{lv.display_get_vertical_resolution(lv.disp)}")
+
+# lv.lodepng_init() not needed
+
+
+# PNG:
+with open("/icon_64x64.png", 'rb') as f:
+ image_data = f.read()
+
+image_dsc = lv.image_dsc_t({
+ 'data_size': len(image_data),
+ 'data': image_data
+})
+
+image1 = lv.image(lv.screen_active())
+image1.set_src(image_dsc)
+image1.set_pos(150,100)
+
+
+
+
+# BIN
+with open("/icon_64x64.bin", 'rb') as f:
+ image_data = f.read()
+
+image_dsc = lv.image_dsc_t({
+ 'data_size': len(image_data),
+ 'data': image_data
+})
+image1 = lv.image(subwindow)
+image1.set_src(image_data)
+image1.set_pos(150,100)
+
+
+
+
+# BIN
+with open("/icon_64x64.bin", 'rb') as f:
+ image_data = f.read()
+
+image_dsc = lv.image_dsc_t({
+ 'data_size': len(image_data),
+ 'data': image_data
+})
+image1 = lv.image(subwindow)
+image1.set_src("A:/icon_64x64.bin")
+image1.set_pos(150,100)
+image1 = lv.image(subwindow)
+image1.set_src("/icon_64x64.bin")
+image1.set_pos(150,100)
+
+# WORKING BIN:
+import lvgl as lv
+import utime
+
+# Initialize LVGL (assuming already done)
+lv.init()
+
+# Load the binary image data (including 12-byte header)
+with open("/icon_64x64.bin", 'rb') as f:
+ image_data = f.read()
+
+# Verify the data size (64x64 RGB565 + 12-byte header = 8204 bytes)
+if len(image_data) != 8204:
+ raise ValueError("Invalid image data size")
+
+# Keep image_data alive to prevent garbage collection
+global_image_data = image_data # Store globally to pin in memory
+
+# Create an lv_image_dsc_t descriptor, letting LVGL parse the header
+image_dsc = lv.image_dsc_t({
+ "header": {
+ "magic": lv.IMAGE_HEADER_MAGIC,
+ "w": 64,
+ "h": 64,
+ "stride": 64 * 2,
+ "cf": lv.COLOR_FORMAT.RGB565
+ },
+ "data": global_image_data, # Entire 8204 bytes (header + pixel data)
+ "data_size": len(image_data) # Total size (8204 bytes)
+})
+
+# Create an image widget
+img = lv.image(lv.screen_active())
+img.set_src(image_dsc) # Set the image source to the descriptor
+img.center() # Center the image on the screen
+
+
+
+
+
+
+
+
+
+try:
+ print("Checking file system...")
+ print(f"Apps dir: {uos.listdir('/apps')}")
+ print(f"App1 dir: {uos.listdir('/apps/com.example.app1')}")
+ print(f"Res dir: {uos.listdir('/apps/com.example.app1/res')}")
+ print(f"Mipmap dir: {uos.listdir('/apps/com.example.app1/res/mipmap-mdpi')}")
+ print(f"File exists: {image_path in uos.listdir('/apps/com.example.app1/res/mipmap-mdpi')}")
+except Exception as e:
+ print(f"File system error: {e}")
+
+# Create a label to ensure something displays
+label = lv.label(lv.screen_active())
+label.set_text("Loading image...")
+label.align(lv.ALIGN.TOP_MID, 0, 10)
+print("Label created")
+
+# Load the image
+
+
+# Load and display the image
+print("Creating image widget...")
+image = lv.image(lv.screen_active())
+try:
+ print(f"Loading image: {image_path}")
+ image.set_src(image_path)
+ image.align(lv.ALIGN.CENTER, 0, 0)
+ print("Image loaded and aligned")
+except Exception as e:
+ print(f"Image load error: {e}")
+ print("Falling back to SYMBOL.DUMMY")
+ image.set_src(lv.SYMBOL.DUMMY)
+ image.align(lv.ALIGN.CENTER, 0, 0)
+
+# Debug image properties
+print(f"Screen active: {lv.screen_active()}")
+print(f"Image widget: {image}")
+print(f"Image source: {image.get_src()}")
+print(f"Image size: {image.get_width()}x{image.get_height()}")
+
+
+
+wifi_icon = lv.label(lv.screen_active())
+wifi_icon.set_text(lv.SYMBOL.WIFI)
+wifi_icon.align(lv.ALIGN.RIGHT_CENTER, 0, 0)
+wifi_icon.set_style_text_color(COLOR_TEXT_WHITE, 0)
+
+
+
+
+
+# Create a label
+label = lv.label(subwindow)
+label.set_text("Monospace Text")
+label.align(lv.ALIGN.CENTER, 0, -40)
+
+# Create a style for the label
+style = lv.style_t()
+style.init()
+style.set_text_font(lv.font_montserrat_12)
+label.add_style(style, 0) # Apply style to the label
+
+
+# Create a label
+label = lv.label(subwindow)
+label.set_text("Monospace Text")
+label.align(lv.ALIGN.CENTER, 0, -20)
+
+# Create a style for the label
+style = lv.style_t()
+style.init()
+style.set_text_font(lv.font_montserrat_14) # Default font
+label.add_style(style, 0) # Apply style to the label
+
+
+
+# Create a label
+label = lv.label(subwindow)
+label.set_text("Monospace Text")
+label.align(lv.ALIGN.CENTER, 0, 0)
+
+# Create a style for the label
+style = lv.style_t()
+style.init()
+style.set_text_font(lv.font_montserrat_16)
+label.add_style(style, 0) # Apply style to the label
+
+
+# Create a label
+label = lv.label(subwindow)
+label.set_text("Monospace Text")
+label.align(lv.ALIGN.CENTER, 0, 40)
+
+# Create a style for the label
+style = lv.style_t()
+style.init()
+style.set_text_font(lv.font_unscii_8) # Set monospace font
+label.add_style(style, 0) # Apply style to the label
+
+# Create a label
+label = lv.label(subwindow)
+label.set_text("Monospace Text")
+label.align(lv.ALIGN.CENTER, 0, 60)
+
+# Create a style for the label
+style = lv.style_t()
+style.init()
+style.set_text_font(lv.font_unscii_16) # Set monospace font
+label.add_style(style, 0) # Apply style to the label
+
+
+
+
+
+# delete folder:
+import mip
+mip.install("shutil")
+import shutil
+shutil.rmtree('/apps/com.example.files')
+
+
+
+import vfs
+vfs.umount('/')
+vfs.VfsLfs2.mkfs(bdev)
+vfs.mount(bdev, '/')
+
+
+
+def memoryview_to_hex_spaced(mv: memoryview) -> str:
+ """Convert the first 50 bytes of a memoryview to a spaced hex string."""
+ sliced = mv[:50]
+ return ' '.join('{:02x}'.format(b & 0xFF) for b in sliced)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+subwindow.clean()
+canary = lv.obj(subwindow)
+canary.add_flag(lv.obj.FLAG.HIDDEN)
+
+subwindow.set_style_bg_color(lv.color_black(), 0)
+
+label = lv.label(subwindow)
+label.set_text("Hello \uf0ac") # Unicode for a symbol (e.g., globe)
+label.align(lv.ALIGN.CENTER, 0, 0)
+
+label2 = lv.label(subwindow)
+label2.set_text("Hello " + lv.SYMBOL.DOWNLOAD)
+label2.align(lv.ALIGN.CENTER, 0, 25)
+
+
+label2 = lv.label(subwindow)
+label2.set_text("Hello " + lv.SYMBOL.DUMMY)
+label2.align(lv.ALIGN.CENTER, 0, 50)
+
+
+image = lv.image(subwindow)
+image.align(lv.ALIGN.CENTER, 0, -50)
+image.set_src(lv.SYMBOL.STOP) # Or use a default image
+
+
+
+
+
+>>> import ota.update
+>>> ota.update.from_file("https://demo.lnpiggy.com/static/firmware/ESP32_GENERIC_S3-SPIRAM_OCT_micropython.bin")
+
+
+temp_zip_path = "/apps/temp.zip"
+print('\nReading file')
+with ZipFile(temp_zip_path) as myzip:
+ with myzip.open('com.example.files/META-INF/MANIFEST.MF') as myfile:
+ print(myfile.read())
+
+
+
+import os
+try:
+ import zipfile
+except ImportError:
+ zipfile = None
+
+
+temp_zip_path = "/apps/temp.zip"
+print('\nReading file')
+with zipfile.ZipFile(temp_zip_path) as myzip:
+ with myzip.open('com.example.files/assets/files.py') as myfile:
+ print(myfile.read())
+
+
+import os
+try:
+ import zipfile
+except ImportError:
+ zipfile = None
+
+temp_zip_path = "/apps/temp.zip"
+print(f"Stat says: {os.stat(temp_zip_path)}")
+with zipfile.ZipFile(temp_zip_path, "r") as zip_ref:
+ print("extracting...")
+ zip_ref.extractall("/apps")
+
+
+
+
+
+
+
+
+
+
+
+subwindow = lv.obj(newscreen)
+subwindow.set_size(TFT_HOR_RES, TFT_VER_RES - NOTIFICATION_BAR_HEIGHT)
+subwindow.set_pos(0, NOTIFICATION_BAR_HEIGHT)
+subwindow.set_style_border_width(0, 0)
+subwindow.set_style_pad_all(0, 0)
+lv.screen_load(newscreen)
+script_globals = {
+ 'lv': lv,
+ 'appscreen': newscreen,
+ 'subwindow': subwindow,
+ 'start_app': start_app, # for launcher apps
+ 'parse_manifest': parse_manifest, # for launcher apps
+ '__name__': "__main__"
+}
diff --git a/draft_code/saved_uasyncio_demo.py b/draft_code/saved_uasyncio_demo.py
new file mode 100644
index 00000000..8c9f2706
--- /dev/null
+++ b/draft_code/saved_uasyncio_demo.py
@@ -0,0 +1,133 @@
+import lvgl as lv
+import uasyncio as asyncio
+import utime
+import gc
+
+# Create a subwindow for the child script (half the 320x240 display)
+screen = lv.screen_active()
+subwindow = lv.obj(screen)
+subwindow.set_size(160, 240) # Half width, full height
+subwindow.align(lv.ALIGN.LEFT_MID, 0, 0) # Left side
+subwindow.set_style_bg_color(lv.color_hex(0xDDDDDD), lv.PART.MAIN)
+
+# Create a label for parent updates
+parent_label = lv.label(screen)
+parent_label.set_text("Parent: 0")
+parent_label.set_style_text_font(lv.font_montserrat_12, 0)
+parent_label.align(lv.ALIGN.TOP_RIGHT, -10, 10)
+
+# Create a parent button
+parent_button = lv.button(screen)
+parent_button.set_size(80, 40)
+parent_button.align(lv.ALIGN.BOTTOM_RIGHT, -10, -50)
+parent_button_label = lv.label(parent_button)
+parent_button_label.set_text("Parent Btn")
+parent_button_label.set_style_text_font(lv.font_montserrat_12, 0)
+
+# Create a parent slider
+parent_slider = lv.slider(screen)
+parent_slider.set_size(100, 10)
+parent_slider.set_range(0, 100)
+parent_slider.align(lv.ALIGN.BOTTOM_RIGHT, -10, -10)
+
+# Parent button callback
+def parent_button_cb(e):
+ print("Parent button clicked")
+
+parent_button.add_event_cb(parent_button_cb, lv.EVENT.CLICKED, None)
+
+# Parent slider callback
+def parent_slider_cb(e):
+ value = parent_slider.get_value()
+ print("Parent slider value:", value)
+
+parent_slider.add_event_cb(parent_slider_cb, lv.EVENT.VALUE_CHANGED, None)
+
+# Function to execute the child script as a coroutine
+async def execute_script(script_source, lvgl_obj):
+ try:
+ script_globals = {
+ 'lv': lv,
+ 'subwindow': lvgl_obj,
+ 'asyncio': asyncio,
+ 'utime': utime
+ }
+ print("Child script: Compiling")
+ code = compile(script_source, "", "exec")
+ exec(code, script_globals)
+ update_child = script_globals.get('update_child')
+ if update_child:
+ print("Child script: Starting update_child")
+ await update_child()
+ else:
+ print("Child script error: No update_child function defined")
+ except Exception as e:
+ print("Child script error:", e)
+
+# Child script buffer: updates label, adds button and slider
+script_buffer = """
+import asyncio
+async def update_child():
+ print("Child coroutine: Creating UI")
+ # Label
+ label = lv.label(subwindow)
+ label.set_text("Child: 0")
+ label.set_style_text_font(lv.font_montserrat_12, 0)
+ label.align(lv.ALIGN.TOP_MID, 0, 10)
+ # Button
+ button = lv.button(subwindow)
+ button.set_size(80, 40)
+ button.align(lv.ALIGN.BOTTOM_MID, 0, -50)
+ button_label = lv.label(button)
+ button_label.set_text("Child Btn")
+ button_label.set_style_text_font(lv.font_montserrat_12, 0)
+ # Slider
+ slider = lv.slider(subwindow)
+ slider.set_size(100, 10)
+ slider.set_range(0, 100)
+ slider.align(lv.ALIGN.BOTTOM_MID, 0, -10)
+ # Button callback
+ def button_cb(e):
+ print("Child button clicked")
+ button.add_event_cb(button_cb, lv.EVENT.CLICKED, None)
+ # Slider callback
+ def slider_cb(e):
+ value = slider.get_value()
+ print("Child slider value:", value)
+ slider.add_event_cb(slider_cb, lv.EVENT.VALUE_CHANGED, None)
+ # Update loop
+ count = 0
+ while True:
+ count += 1
+ print("Child coroutine: Updating label to", count)
+ label.set_text(f"Child: {count}")
+ await asyncio.sleep_ms(2000) # Update every 2s
+"""
+
+# Parent coroutine: updates parent label every 1 second
+async def update_parent():
+ count = 0
+ while True:
+ count += 1
+ print("Parent coroutine: Updating label to", count)
+ parent_label.set_text(f"Parent: {count}")
+ gc.collect()
+ print("Parent coroutine: Free memory:", gc.mem_free())
+ await asyncio.sleep_ms(1000) # Update every 1s
+
+# Main async function to run all tasks
+async def main():
+ print("Main: Starting tasks")
+ asyncio.create_task(update_parent())
+ asyncio.create_task(execute_script(script_buffer, subwindow))
+ while True:
+ await asyncio.sleep_ms(100)
+
+# Run the event loop
+gc.collect()
+print("Free memory before loop:", gc.mem_free())
+try:
+ asyncio.run(main())
+except Exception as e:
+ print("Main error:", e)
+
diff --git a/draft_code/simple_thread.py b/draft_code/simple_thread.py
new file mode 100644
index 00000000..ab6c8960
--- /dev/null
+++ b/draft_code/simple_thread.py
@@ -0,0 +1,77 @@
+import _thread
+
+# Function to execute the child script as a coroutine
+def execute_script(script_source, lvgl_obj):
+ try:
+ script_globals = {
+ 'lv': lv,
+ 'subwindow': lvgl_obj,
+ }
+ print("Child script: Compiling")
+ code = compile(script_source, "", "exec")
+ exec(code, script_globals)
+ app_main = script_globals.get('app_main')
+ if app_main:
+ print("Child script: Starting app_main")
+ app_main()
+ print("Script finished!")
+ else:
+ print("Child script error: No app_main function defined")
+ except Exception as e:
+ print("Child script error:", e)
+
+
+# Child script buffer: updates label, adds button and slider
+script_buffer = """
+import time
+def app_main():
+ print("Child coroutine: Creating UI")
+ # Label
+ label = lv.label(subwindow)
+ label.set_text("Child: 0")
+ label.set_style_text_font(lv.font_montserrat_12, 0)
+ label.align(lv.ALIGN.TOP_MID, 0, 10)
+ # Button
+ button = lv.button(subwindow)
+ button.set_size(80, 40)
+ button.align(lv.ALIGN.CENTER, 0, 0)
+ button_label = lv.label(button)
+ button_label.set_text("Quit")
+ button_label.set_style_text_font(lv.font_montserrat_12, 0)
+ # Slider
+ slider = lv.slider(subwindow)
+ slider.set_range(0, 100)
+ slider.align(lv.ALIGN.BOTTOM_MID, 0, -30)
+ # Quit flag
+ should_continue = True
+ # Button callback
+ def button_cb(e):
+ nonlocal should_continue
+ print("Quit button clicked, exiting child")
+ should_continue = False
+ button.add_event_cb(button_cb, lv.EVENT.CLICKED, None)
+ # Slider callback
+ def slider_cb(e):
+ value = slider.get_value()
+ print("Child slider value:", value)
+ slider.add_event_cb(slider_cb, lv.EVENT.VALUE_CHANGED, None)
+ # Update loop
+ count = 0
+ while should_continue:
+ count += 1
+ print("Child coroutine: Updating label to", count)
+ label.set_text(f"Child: {count}")
+ time.sleep_ms(1000)
+ print("Child coroutine: Exiting")
+"""
+
+
+# Start the event loop in a background thread
+gc.collect()
+print("Free memory before loop:", gc.mem_free())
+try:
+ _thread.stack_size(8192)
+ _thread.start_new_thread(execute_script, (script_buffer, subwindow))
+ print("Event loop started in background thread")
+except Exception as e:
+ print("Error starting event loop thread:", e)
diff --git a/draft_code/video_player/README.md b/draft_code/video_player/README.md
new file mode 100644
index 00000000..d9a12061
--- /dev/null
+++ b/draft_code/video_player/README.md
@@ -0,0 +1,2 @@
+This doesn't work because the build with ffmpeg in it fails,
+because it needs that STDIO MEM allocator in lib/lv_conf.h and that has a compilation issue.
diff --git a/draft_code/video_player/convert_video.sh b/draft_code/video_player/convert_video.sh
new file mode 100755
index 00000000..324594a2
--- /dev/null
+++ b/draft_code/video_player/convert_video.sh
@@ -0,0 +1,3 @@
+# https://sample-videos.com/
+
+ffmpeg -i SampleVideo_640x360_1mb.mp4 -c:v mjpeg -q:v 7 -vf "fps=15,scale=320:180:flags=lanczos" -c:a pcm_u8 video_320x180.avi
diff --git a/trash/appstore.py b/trash/appstore.py
deleted file mode 100644
index e48c4788..00000000
--- a/trash/appstore.py
+++ /dev/null
@@ -1,538 +0,0 @@
-from machine import Pin, SPI
-import st7789
-import lcd_bus
-import machine
-import cst816s
-import i2c
-import urequests
-
-import lvgl as lv
-import task_handler
-
-# Pin configuration
-SPI_BUS = 2
-SPI_FREQ = 40000000
-LCD_SCLK = 39
-LCD_MOSI = 38
-LCD_MISO = 40
-LCD_DC = 42
-LCD_CS = 45
-LCD_BL = 1
-
-I2C_BUS = 0
-I2C_FREQ = 100000
-TP_SDA = 48
-TP_SCL = 47
-TP_ADDR = 0x15
-TP_REGBITS = 8
-
-TFT_HOR_RES=320
-TFT_VER_RES=240
-
-#lv.init() not needed
-spi_bus = machine.SPI.Bus(
- host=SPI_BUS,
- mosi=LCD_MOSI,
- miso=LCD_MISO,
- sck=LCD_SCLK
-)
-display_bus = lcd_bus.SPIBus(
- spi_bus=spi_bus,
- freq=SPI_FREQ,
- dc=LCD_DC,
- cs=LCD_CS,
-)
-display = st7789.ST7789(
- data_bus=display_bus,
- display_width=TFT_VER_RES,
- display_height=TFT_HOR_RES,
- backlight_pin=LCD_BL,
- backlight_on_state=STATE_PWM,
- color_space=lv.COLOR_FORMAT.RGB565,
- color_byte_order=st7789.BYTE_ORDER_BGR,
- rgb565_byte_swap=True,
-)
-display.init()
-display.set_power(True)
-display.set_backlight(100)
-
-# Touch handling:
-i2c_bus = i2c.I2C.Bus(host=I2C_BUS, scl=TP_SCL, sda=TP_SDA, freq=I2C_FREQ, use_locks=False)
-touch_dev = i2c.I2C.Device(bus=i2c_bus, dev_id=TP_ADDR, reg_bits=TP_REGBITS)
-indev=cst816s.CST816S(touch_dev,startup_rotation=lv.DISPLAY_ROTATION._180) # button in top left, good
-
-task_handler.TaskHandler()
-
-#def task_thread():
-# task_handler.TaskHandler()
-#import _thread
-#_thread.start_new_thread(task_thread, ())
-
-display.set_rotation(lv.DISPLAY_ROTATION._90)
-
-# Custom touch interrupt handler:
-indev._write_reg(0xEC,0x06)
-indev._write_reg(0xFA,0x50)
-irq_pin=machine.Pin(46,machine.Pin.IN,machine.Pin.PULL_UP)
-# gesture ids:
-# 0: press
-# 1: swipe from left to USB port
-# 2: swipe from USB port to left
-# 3: top to bottom
-# 4: bottom to top
-# 5: release
-# 12: long press
-def handle_gesture(pin):
- indev._read_reg(0x01)
- gesture_id=indev._rx_buf[0]
- indev._read_reg(0x02)
- finger_num=indev._rx_buf[0]
- indev._read_reg(0x03)
- x_h=indev._rx_buf[0]
- indev._read_reg(0x04)
- x_l=indev._rx_buf[0]
- x=((x_h&0x0F)<<8)|x_l
- indev._read_reg(0x05)
- y_h=indev._rx_buf[0]
- indev._read_reg(0x06)
- y_l=indev._rx_buf[0]
- y=((y_h&0x0F)<<8)|y_l
- #print(f"GestureID={gesture_id},FingerNum={finger_num},X={x},Y={y}")
- if gesture_id==0x04:
- #print("Swipe Up Detected")
- close_drawer()
- elif gesture_id==0x03:
- #print("Swipe Down Detected")
- open_drawer()
-
-irq_pin.irq(trigger=machine.Pin.IRQ_FALLING,handler=handle_gesture)
-
-
-
-
-
-
-
-# GUI:
-# Below works at https://sim.lvgl.io/v9.0/micropython/ports/webassembly/index.html
-
-import time
-
-# Constants
-TFT_HOR_RES=320
-TFT_VER_RES=240
-NOTIFICATION_BAR_HEIGHT=24
-BUTTON_WIDTH=100
-BUTTON_HEIGHT=40
-PADDING_TINY=5
-PADDING_SMALL=10
-PADDING_MEDIUM=20
-PADDING_LARGE=30
-DRAWER_ANIM_DURATION=300
-SLIDER_MIN_VALUE=1
-SLIDER_MAX_VALUE=100
-SLIDER_DEFAULT_VALUE=80
-OFFSET_WIFI_ICON = -60
-OFFSET_BATTERY_ICON = -40
-TIME_UPDATE_INTERVAL = 1000
-
-# Color palette
-DARKPINK = lv.color_hex(0xEC048C)
-MEDIUMPINK = lv.color_hex(0xF480C5)
-LIGHTPINK = lv.color_hex(0xF9E9F2)
-DARKYELLOW = lv.color_hex(0xFBDC05)
-LIGHTYELLOW = lv.color_hex(0xFBE499)
-PUREBLACK = lv.color_hex(0x000000)
-
-COLOR_DRAWER_BG=MEDIUMPINK
-COLOR_TEXT_WHITE=LIGHTPINK
-COLOR_NOTIF_BAR_BG = DARKPINK
-COLOR_DRAWER_BUTTON_BG=DARKYELLOW
-COLOR_DRAWER_BUTTONTEXT=PUREBLACK
-COLOR_SLIDER_BG=LIGHTPINK
-COLOR_SLIDER_KNOB=DARKYELLOW
-COLOR_SLIDER_INDICATOR=LIGHTPINK
-
-
-
-drawer=None
-wifi_screen=None
-drawer_open=False
-
-scr = lv.screen_active()
-scr.set_style_bg_color(lv.color_hex(0x000000), 0)
-
-
-
-def open_drawer():
- global drawer_open
- if not drawer_open:
- drawer.set_y(NOTIFICATION_BAR_HEIGHT)
- drawer_open=True
-
-
-def close_drawer():
- global drawer_open
- if drawer_open:
- drawer.set_y(-TFT_VER_RES+NOTIFICATION_BAR_HEIGHT)
- drawer_open=False
-
-
-def toggle_drawer(event):
- global drawer_open
- if drawer_open:
- close_drawer()
- else:
- open_drawer()
-
-# Create notification bar object
-notification_bar = lv.obj(lv.screen_active())
-notification_bar.set_style_bg_color(COLOR_NOTIF_BAR_BG, 0)
-notification_bar.set_size(320, NOTIFICATION_BAR_HEIGHT)
-notification_bar.set_pos(0, 0)
-notification_bar.set_scrollbar_mode(lv.SCROLLBAR_MODE.OFF)
-notification_bar.set_scroll_dir(lv.DIR.VER)
-notification_bar.set_style_border_width(0, 0)
-notification_bar.set_style_radius(0, 0)
-# Time label
-time_label = lv.label(notification_bar)
-time_label.set_text("12:00")
-time_label.align(lv.ALIGN.LEFT_MID, PADDING_TINY, 0)
-time_label.set_style_text_color(COLOR_TEXT_WHITE, 0)
-# Notification icon (bell)
-notif_icon = lv.label(notification_bar)
-notif_icon.set_text(lv.SYMBOL.BELL)
-notif_icon.align_to(time_label, lv.ALIGN.OUT_RIGHT_MID, PADDING_LARGE, 0)
-notif_icon.set_style_text_color(COLOR_TEXT_WHITE, 0)
-# WiFi icon
-wifi_icon = lv.label(notification_bar)
-wifi_icon.set_text(lv.SYMBOL.WIFI)
-wifi_icon.align(lv.ALIGN.RIGHT_MID, OFFSET_WIFI_ICON, 0)
-wifi_icon.set_style_text_color(COLOR_TEXT_WHITE, 0)
-# Battery icon
-battery_icon = lv.label(notification_bar)
-battery_icon.set_text(lv.SYMBOL.BATTERY_FULL)
-battery_icon.align(lv.ALIGN.RIGHT_MID, OFFSET_BATTERY_ICON, 0)
-battery_icon.set_style_text_color(COLOR_TEXT_WHITE, 0)
-# Battery percentage
-battery_label = lv.label(notification_bar)
-battery_label.set_text("100%")
-battery_label.align(lv.ALIGN.RIGHT_MID, 0, 0)
-battery_label.set_style_text_color(COLOR_TEXT_WHITE, 0)
-# Timer to update time every second
-def update_time(timer):
- ticks = time.ticks_ms()
- hours = (ticks // 3600000) % 24
- minutes = (ticks // 60000) % 60
- seconds = (ticks // 1000) % 60
- time_label.set_text(f"{hours:02d}:{minutes:02d}:{seconds:02d}")
-lv.timer_create(update_time, TIME_UPDATE_INTERVAL, None)
-notification_bar.add_event_cb(toggle_drawer, lv.EVENT.CLICKED, None)
-
-
-
-
-
-# Subwindow is created before drawer so that drawer is on top
-screen = lv.screen_active()
-subwindow = lv.obj(screen)
-subwindow.set_size(TFT_HOR_RES, TFT_VER_RES - NOTIFICATION_BAR_HEIGHT)
-subwindow.set_pos(0, NOTIFICATION_BAR_HEIGHT)
-subwindow.set_style_border_width(0, 0)
-subwindow.set_style_pad_all(0, 0)
-
-
-
-def create_drawer():
- global drawer,wifi_screen
- drawer=lv.obj(lv.screen_active())
- drawer.set_size(TFT_HOR_RES,TFT_VER_RES-NOTIFICATION_BAR_HEIGHT)
- drawer.set_pos(0,-TFT_VER_RES+NOTIFICATION_BAR_HEIGHT)
- drawer.set_style_bg_color(COLOR_DRAWER_BG,0)
- drawer.set_scroll_dir(lv.DIR.NONE)
- slider=lv.slider(drawer)
- slider.set_range(SLIDER_MIN_VALUE,SLIDER_MAX_VALUE)
- slider.set_value(SLIDER_DEFAULT_VALUE,False)
- slider.set_width(TFT_HOR_RES-PADDING_MEDIUM)
- slider.align(lv.ALIGN.TOP_MID,0,PADDING_SMALL)
- slider.set_style_bg_color(COLOR_SLIDER_BG,lv.PART.MAIN)
- slider.set_style_bg_color(COLOR_SLIDER_INDICATOR,lv.PART.INDICATOR)
- slider.set_style_bg_color(COLOR_SLIDER_KNOB,lv.PART.KNOB)
- slider_label=lv.label(drawer)
- slider_label.set_text("80%")
- slider_label.set_style_text_color(COLOR_TEXT_WHITE,0)
- slider_label.align_to(slider,lv.ALIGN.OUT_TOP_MID,0,-5)
- # works here
- def slider_event(e):
- slider=e.get_target()
- label=e.get_user_data()
- value=slider.get_value()
- label.set_text(f"{value}%")
- display.set_backlight(value)
- # this crashes it: slider.add_event_cb(slider_event,lv.EVENT.VALUE_CHANGED,slider_label)
- # this crashes it: slider.add_event_cb(slider_event,lv.EVENT.VALUE_CHANGED,slider_label)
- wifi_btn=lv.button(drawer)
- wifi_btn.set_size(BUTTON_WIDTH,BUTTON_HEIGHT)
- wifi_btn.align(lv.ALIGN.LEFT_MID,PADDING_SMALL,0)
- wifi_btn.set_style_bg_color(COLOR_DRAWER_BUTTON_BG,0)
- wifi_label=lv.label(wifi_btn)
- wifi_label.set_text(lv.SYMBOL.WIFI+" WiFi")
- wifi_label.center()
- wifi_label.set_style_text_color(COLOR_DRAWER_BUTTONTEXT,0)
- def wifi_event(e):
- global drawer_open
- #wifi_screen.set_y(0) # TODO: make this
- close_drawer()
- drawer_open=False
- wifi_btn.add_event_cb(wifi_event,lv.EVENT.CLICKED,None)
- #
- settings_btn=lv.button(drawer)
- settings_btn.set_size(BUTTON_WIDTH,BUTTON_HEIGHT)
- settings_btn.align(lv.ALIGN.RIGHT_MID,-PADDING_SMALL,0)
- settings_btn.set_style_bg_color(COLOR_DRAWER_BUTTON_BG,0)
- settings_label=lv.label(settings_btn)
- settings_label.set_text(lv.SYMBOL.SETTINGS+" Settings")
- settings_label.center()
- settings_label.set_style_text_color(COLOR_DRAWER_BUTTONTEXT,0)
- def settings_event(e):
- global drawer_open
- close_drawer()
- drawer_open=False
- settings_btn.add_event_cb(settings_event,lv.EVENT.CLICKED,None)
- #
- launcher_btn=lv.button(drawer)
- launcher_btn.set_size(BUTTON_WIDTH,BUTTON_HEIGHT)
- launcher_btn.align(lv.ALIGN.BOTTOM_LEFT,PADDING_SMALL,0)
- launcher_btn.set_style_bg_color(COLOR_DRAWER_BUTTON_BG,0)
- launcher_label=lv.label(launcher_btn)
- launcher_label.set_text(lv.SYMBOL.HOME+" Launcher")
- launcher_label.center()
- launcher_label.set_style_text_color(COLOR_DRAWER_BUTTONTEXT,0)
- def launcher_event(e):
- print("Launcher button pressed!")
- global drawer_open
- close_drawer()
- drawer_open=False
- run_app(launcher_script,False)
- launcher_btn.add_event_cb(launcher_event,lv.EVENT.CLICKED,None)
- #
- restart_btn=lv.button(drawer)
- restart_btn.set_size(BUTTON_WIDTH,BUTTON_HEIGHT)
- restart_btn.align(lv.ALIGN.BOTTOM_RIGHT,-PADDING_SMALL,0)
- restart_btn.set_style_bg_color(COLOR_DRAWER_BUTTON_BG,0)
- restart_label=lv.label(restart_btn)
- restart_label.set_text(lv.SYMBOL.POWER+" Reset")
- restart_label.center()
- restart_label.set_style_text_color(COLOR_DRAWER_BUTTONTEXT,0)
- def launcher_event(e):
- print("Reset button pressed!")
- global drawer_open
- close_drawer()
- drawer_open=False
- run_app(launcher_script,False)
- restart_btn.add_event_cb(lambda event: machine.reset(),lv.EVENT.CLICKED,None)
-
-
-create_drawer()
-
-
-
-
-
-
-# uasyncio and _thread aren't available on web
-
-import _thread
-
-# Function to execute the child script as a coroutine
-def execute_script(script_source, is_file, lvgl_obj, return_to_launcher):
- thread_id = _thread.get_ident();
- print(f"Thread {thread_id}: executing script")
- try:
- script_globals = {
- 'lv': lv,
- 'subwindow': lvgl_obj,
- 'run_app': run_app,
- 'app1_script': app1_script,
- 'app2_script': app2_script
- }
- if is_file:
- print(f"Thread {thread_id}: reading script from file: {script_source}")
- with open(script_source, 'r') as f:
- script_source = f.read()
- print(f"Thread {thread_id}: starting script")
- exec(script_source, script_globals)
- print(f"Thread {thread_id}: script finished")
- if return_to_launcher:
- print(f"Thread {thread_id}: running launcher_script")
- run_app(launcher_script,False,False)
- except Exception as e:
- print(f"Thread {thread_id}: error ", e)
-
-
-def run_app(scriptname,is_file,return_to_launcher=True):
- # Start the event loop in a background thread
- gc.collect()
- print("Free memory before starting new script thread:", gc.mem_free())
- try:
- subwindow.clean()
- # 168KB maximum at startup but 136KB after loading display, drivers, LVGL gui etc so let's go for 128KB for now, still a lot...
- # But then no additional threads can be created. So 32KB seems like a good balance, allowing for 4 threads in apps...
- #_thread.stack_size(32768)
- _thread.stack_size(16384)
- _thread.start_new_thread(execute_script, (scriptname, False, subwindow, return_to_launcher))
- print("Event loop started in background thread")
- except Exception as e:
- print("Error starting event loop thread:", e)
-
-
-# app1: updates label, adds button and slider
-app1_script = """
-import time
-print("Child coroutine: Creating UI")
-# Label
-label = lv.label(subwindow)
-label.set_text("App1: 0")
-label.align(lv.ALIGN.TOP_MID, 0, 10)
-# Button
-button = lv.button(subwindow)
-button.set_size(100, 60)
-button.align(lv.ALIGN.CENTER, 0, 0)
-button_label = lv.label(button)
-button_label.set_text("Quit")
-button_label.center()
-# Slider
-slider = lv.slider(subwindow)
-slider.set_range(0, 100)
-slider.set_value(50, lv.ANIM.OFF)
-slider.align(lv.ALIGN.BOTTOM_MID, 0, -30)
-# Quit flag
-should_continue = True
-# Button callback
-def button_cb(e):
- global should_continue
- print("Quit button clicked, exiting child")
- should_continue = False
-button.add_event_cb(button_cb, lv.EVENT.CLICKED, None)
-# Slider callback
-def slider_cb(e):
- value = slider.get_value()
- #print("Child slider value:", value)
-slider.add_event_cb(slider_cb, lv.EVENT.VALUE_CHANGED, None)
-# Update loop
-count = 0
-while should_continue:
- count += 1
- #print("Child coroutine: Updating label to", count)
- label.set_text(f"App1: {count}")
- time.sleep_ms(100) # shorter makes it more responive to the quit button
-print("Child coroutine: Exiting")
-"""
-
-app2_script = """
-import time
-import _thread
-print("App2 running")
-
-# Quit flag
-should_continue = True
-
-canary = lv.obj(subwindow)
-canary.add_flag(0x0001) # LV_OBJ_FLAG_HIDDEN is 0x0001 (don't know why I can't find it!)
-
-def app2_thread():
- count=0
- while should_continue and canary.get_class():
- print(f"app2_thread: thread_id {_thread.get_ident()} - {count}")
- count+=1
- time.sleep(1)
-
-_thread.start_new_thread(app2_thread, ())
-
-
-# Label
-label = lv.label(subwindow)
-label.set_text("App2: 0")
-label.align(lv.ALIGN.TOP_MID, 0, 10)
-# Button
-button = lv.button(subwindow)
-button.set_size(100, 60)
-button.align(lv.ALIGN.CENTER, 0, 0)
-button_label = lv.label(button)
-button_label.set_text("Quit")
-button_label.center()
-# Slider
-slider = lv.slider(subwindow)
-slider.set_range(0, 100)
-slider.set_value(50, lv.ANIM.OFF)
-slider.align(lv.ALIGN.BOTTOM_MID, 0, -30)
-# Button callback
-def button_cb(e):
- global should_continue
- print("Quit button clicked, exiting child")
- should_continue = False
-button.add_event_cb(button_cb, lv.EVENT.CLICKED, None)
-# Slider callback
-def slider_cb(e):
- value = slider.get_value()
- #print("Child slider value:", value)
-slider.add_event_cb(slider_cb, lv.EVENT.VALUE_CHANGED, None)
-# Update loop
-count = 0
-while should_continue:
- count += 1
- #print("Child coroutine: Updating label to", count)
- label.set_text(f"App2: {count}")
- time.sleep_ms(1000) # shorter makes it more responive to the quit button
-print("Child coroutine: Exiting")
-"""
-
-
-launcher_script = """
-print("Launcher script running")
-app1_button = lv.button(subwindow)
-app1_button.set_size(120, 40)
-app1_button.align(lv.ALIGN.LEFT_MID, 20, 0)
-app1_button_label = lv.label(app1_button)
-app1_button_label.set_text("Start App 1")
-app1_button_label.center()
-app1_button.add_event_cb(lambda event: run_app(app1_script,False), lv.EVENT.CLICKED, None)
-app2_button = lv.button(subwindow)
-app2_button.set_size(120, 40)
-app2_button.align(lv.ALIGN.RIGHT_MID, -20, 0)
-app2_button_label = lv.label(app2_button)
-app2_button_label.set_text("Start App 2")
-app2_button_label.center()
-app2_button.add_event_cb(lambda event: run_app(app2_script,False), lv.EVENT.CLICKED, None)
-print("Launcher script exiting")
-"""
-
-
-run_app(launcher_script,False,False)
-
-
-
-
-import network
-import time
-
-# Connect to Wi-Fi
-def connect_wifi():
- wlan = network.WLAN(network.STA_IF)
- wlan.active(True)
- wlan.connect("SSIDHERE", "PASSWORDHERE")
- print("Connecting to Wi-Fi...", end="")
- for _ in range(30): # Wait up to 30 seconds
- if wlan.isconnected():
- print(" Connected!")
- print("IP:", wlan.ifconfig()[0])
- return True
- time.sleep(1)
- print(".", end="")
- print(" Failed to connect!")
- return False
-
-
-#connect_wifi()
-
-