Files
MicroPythonOS/draft_code/apps.py_dont_compile_mpy_dontwork
T
Thomas Farstrike f2ac71d6f2 Add draft_code
2025-06-13 09:55:17 +02:00

450 lines
18 KiB
Plaintext

import lvgl as lv
import uio
import ujson
import uos
import _thread
import traceback
import mpos.info
import mpos.ui
def good_stack_size():
stacksize = 24*1024
import sys
if sys.platform == "esp32":
stacksize = 16*1024
return stacksize
# Run the script in the current thread:
def execute_script(script_source, is_file, cwd=None, classname=None):
thread_id = _thread.get_ident()
compile_name = 'script' if not is_file else script_source
print(f"Thread {thread_id}: executing script with cwd: {cwd}")
script_globals = {'lv': lv, '__name__': "__main__"}
import sys
import uos
import utime
path_before = sys.path
if cwd:
sys.path.append(cwd)
try:
if is_file:
mpy_file = script_source.rsplit('.py', 1)[0] + '.mpy' if '.py' in script_source else script_source + '.mpy'
try:
uos.stat(mpy_file)
source_file = mpy_file
except OSError:
source_file = script_source
mode = 'rb' if source_file.endswith('.mpy') else 'r'
print(f"Thread {thread_id}: reading {'bytecode' if mode == 'rb' else 'script'} from {source_file}")
start_time = utime.ticks_ms()
f = open(source_file, mode)
try:
script_source = f.read()
finally:
f.close()
read_time = utime.ticks_diff(utime.ticks_ms(), start_time)
print(f"Thread {thread_id}: file read took {read_time} ms")
try:
start_time = utime.ticks_ms()
compiled_script = script_source if isinstance(script_source, bytes) else compile(script_source, compile_name, 'exec')
compile_time = utime.ticks_diff(utime.ticks_ms(), start_time)
if not isinstance(script_source, bytes):
print(f"Thread {thread_id}: compilation took {compile_time} ms")
exec(compiled_script, script_globals)
if classname:
main_activity = script_globals.get(classname)
if main_activity:
Activity.startActivity(None, Intent(activity_class=main_activity))
else:
print("Warning: could not find main_activity")
except Exception as e:
print(f"Thread {thread_id}: exception during execution:")
traceback.print_exception(type(e), e, getattr(e, '__traceback__', None))
print(f"Thread {thread_id}: script {compile_name} finished")
except Exception as e:
print(f"Thread {thread_id}: error:")
traceback.print_exception(type(e), e, getattr(e, '__traceback__', None))
sys.path = path_before
# Run the script in a new thread:
# TODO: check if the script exists here instead of launching a new thread?
def execute_script_new_thread(scriptname, is_file):
print(f"main.py: execute_script_new_thread({scriptname},{is_file})")
try:
# 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. A stacksize of 32KB allows for 4 threads, so 3 in the app itself, which might be tight.
# 16KB allows for 10 threads in the apps, but seems too tight for urequests on unix (desktop) targets
# 32KB seems better for the camera, but it forced me to lower other app threads from 16 to 12KB
#_thread.stack_size(24576) # causes camera issue...
# NOTE: This doesn't do anything if apps are started in the same thread!
if "camtest" in scriptname:
print("Starting camtest with extra stack size!")
stack=32*1024
elif "appstore"in scriptname:
print("Starting appstore with extra stack size!")
stack=24*1024 # this doesn't do anything because it's all started in the same thread
else:
stack=16*1024 # 16KB doesn't seem to be enough for the AppStore app on desktop
stack = mpos.apps.good_stack_size()
print(f"app.py: setting stack size for script to {stack}")
_thread.stack_size(stack)
_thread.start_new_thread(execute_script, (scriptname, is_file))
except Exception as e:
print("main.py: execute_script_new_thread(): error starting new thread thread: ", e)
def start_app_by_name(app_name, is_launcher=False):
mpos.ui.set_foreground_app(app_name)
custom_app_dir=f"apps/{app_name}"
builtin_app_dir=f"builtin/apps/{app_name}"
try:
stat = uos.stat(custom_app_dir)
start_app(custom_app_dir, is_launcher)
except OSError:
start_app(builtin_app_dir, is_launcher)
def start_app(app_dir, is_launcher=False):
print(f"main.py start_app({app_dir},{is_launcher})")
mpos.ui.set_foreground_app(app_dir) # would be better to store only the app name...
manifest_path = f"{app_dir}/META-INF/MANIFEST.JSON"
app = mpos.apps.parse_manifest(manifest_path)
print(f"start_app parsed manifest and got: {str(app)}")
main_launcher_activity = find_main_launcher_activity(app)
if not main_launcher_activity:
print(f"WARNING: can't start {app_dir} because no main_launcher_activity was found.")
return
start_script_fullpath = f"{app_dir}/{main_launcher_activity.get('entrypoint')}"
execute_script(start_script_fullpath, True, app_dir + "/assets/", main_launcher_activity.get("classname"))
# Launchers have the bar, other apps don't have it
if is_launcher:
mpos.ui.open_bar()
else:
mpos.ui.close_bar()
def restart_launcher():
mpos.ui.empty_screen_stack()
# No need to stop the other launcher first, because it exits after building the screen
start_app_by_name("com.micropythonos.launcher", True) # Would be better to query the PackageManager for Activities that are launchers
def find_main_launcher_activity(app):
result = None
for activity in app.activities:
if not activity.get("entrypoint") or not activity.get("classname"):
print(f"Warning: activity {activity} has no entrypoint and classname, skipping...")
continue
print("checking activity's intent_filters...")
for intent_filter in activity.get("intent_filters"):
print("checking intent_filter...")
if intent_filter.get("action") == "main" and intent_filter.get("category") == "launcher":
print("found main_launcher!")
result = activity
break
return result
def is_launcher(app_name):
print(f"checking is_launcher for {app_name}")
# Simple check, could be more elaborate by checking the MANIFEST.JSON for the app...
return "launcher" in app_name
class App:
def __init__(self, name, publisher, short_description, long_description, icon_url, download_url, fullname, version, category, activities):
self.name = name
self.publisher = publisher
self.short_description = short_description
self.long_description = long_description
self.icon_url = icon_url
self.download_url = download_url
self.fullname = fullname
self.version = version
self.category = category
self.image = None
self.image_dsc = None
self.activities = activities
def __str__(self):
return (f"App(name='{self.name}', "
f"publisher='{self.publisher}', "
f"short_description='{self.short_description}', "
f"version='{self.version}', "
f"category='{self.category}', "
f"activities={self.activities})")
def parse_manifest(manifest_path):
# Default values for App object
default_app = App(
name="Unknown",
publisher="Unknown",
short_description="",
long_description="",
icon_url="",
download_url="",
fullname="Unknown",
version="0.0.0",
category="",
activities=[]
)
try:
with open(manifest_path, 'r') as f:
app_info = ujson.load(f)
#print(f"parsed app: {app_info}")
# Create App object with values from manifest, falling back to defaults
return App(
name=app_info.get("name", default_app.name),
publisher=app_info.get("publisher", default_app.publisher),
short_description=app_info.get("short_description", default_app.short_description),
long_description=app_info.get("long_description", default_app.long_description),
icon_url=app_info.get("icon_url", default_app.icon_url),
download_url=app_info.get("download_url", default_app.download_url),
fullname=app_info.get("fullname", default_app.fullname),
version=app_info.get("version", default_app.version),
category=app_info.get("category", default_app.category),
activities=app_info.get("activities", default_app.activities)
)
except OSError:
print(f"parse_manifest: error loading manifest_path: {manifest_path}")
return default_app
def auto_connect():
builtin_auto_connect = "builtin/system/WifiService.py"
try:
print(f"Starting {builtin_auto_connect}...")
stat = uos.stat(builtin_auto_connect)
execute_script_new_thread(builtin_auto_connect, True)
except Exception as e:
print("Couldn't execute {builtin_auto_connect} because exception {e}, continuing...")
class Activity:
def __init__(self):
self.intent = None # Store the intent that launched this activity
self.result = None
self._result_callback = None
def onCreate(self):
pass
def onStart(self, screen):
pass
def onResume(self, screen):
pass
def onPause(self, screen):
pass
def onStop(self, screen):
pass
def onDestroy(self, screen):
pass
def setContentView(self, screen):
mpos.ui.setContentView(self, screen)
def startActivity(self, intent):
ActivityNavigator.startActivity(intent)
def startActivityForResult(self, intent, result_callback):
ActivityNavigator.startActivityForResult(intent, result_callback)
def initError(self, e):
print(f"WARNING: You might have inherited from Activity with a custom __init__() without calling super().__init__(). Got AttributeError: {e}")
def getIntent(self):
try:
return self.intent
except AttributeError as e:
self.initError(e)
def setResult(self, result_code, data=None):
"""Set the result to be returned when the activity finishes."""
try:
self.result = {"result_code": result_code, "data": data or {}}
except AttributeError as e:
self.initError(e)
def finish(self):
mpos.ui.back_screen()
try:
if self._result_callback and self.result:
self._result_callback(self.result)
self._result_callback = None # Clean up
except AttributeError as e:
self.initError(e)
class Intent:
def __init__(self, activity_class=None, action=None, data=None, extras=None):
self.activity_class = activity_class # Explicit target (e.g., SettingsActivity)
self.action = action # Action string (e.g., "view", "share")
self.data = data # Single data item (e.g., URL)
self.extras = extras or {} # Dictionary for additional data
self.flags = {} # Simplified flags: {"clear_top": bool, "no_history": bool, "no_animation": bool}
def addFlag(self, flag, value=True):
self.flags[flag] = value
return self
def putExtra(self, key, value):
self.extras[key] = value
return self
class ActivityNavigator:
@staticmethod
def startActivity(intent):
if not isinstance(intent, Intent):
raise ValueError("Must provide an Intent")
if intent.action: # Implicit intent: resolve handlers
handlers = APP_REGISTRY.get(intent.action, [])
if len(handlers) == 1:
intent.activity_class = handlers[0]
ActivityNavigator._launch_activity(intent)
elif handlers:
ActivityNavigator._show_chooser(intent, handlers)
else:
raise ValueError(f"No handlers for action: {intent.action}")
else:
ActivityNavigator._launch_activity(intent)
@staticmethod
def startActivityForResult(intent, result_callback):
"""Launch an activity and pass a callback for the result."""
if not isinstance(intent, Intent):
raise ValueError("Must provide an Intent")
if intent.action: # Implicit intent: resolve handlers
handlers = APP_REGISTRY.get(intent.action, [])
if len(handlers) == 1:
intent.activity_class = handlers[0]
return ActivityNavigator._launch_activity(intent, result_callback)
elif handlers:
ActivityNavigator._show_chooser(intent, handlers)
return None # Chooser handles result forwarding
else:
raise ValueError(f"No handlers for action: {intent.action}")
else:
return ActivityNavigator._launch_activity(intent, result_callback)
@staticmethod
def _launch_activity(intent, result_callback=None):
"""Launch an activity and set up result callback."""
activity = intent.activity_class()
activity.intent = intent
activity._result_callback = result_callback # Pass callback to activity
activity.onCreate()
return activity
@staticmethod
def _show_chooser(intent, handlers):
chooser_intent = Intent(ChooserActivity, extras={"original_intent": intent, "handlers": [h.__name__ for h in handlers]})
ActivityNavigator._launch_activity(chooser_intent)
class ChooserActivity(Activity):
def __init__(self):
super().__init__()
def onCreate(self):
screen = lv.obj()
# Get handlers from intent extras
original_intent = self.getIntent().extras.get("original_intent")
handlers = self.getIntent().extras.get("handlers", [])
label = lv.label(screen)
label.set_text("Choose an app")
label.set_pos(10, 10)
for i, handler_name in enumerate(handlers):
btn = lv.btn(screen)
btn.set_user_data(f"handler_{i}")
btn_label = lv.label(btn)
btn_label.set_text(handler_name)
btn.set_pos(10, 50 * (i + 1) + 10)
btn.add_event_cb(lambda e, h=handler_name, oi=original_intent: self._select_handler(h, oi), lv.EVENT.CLICKED)
self.setContentView(screen)
def _select_handler(self, handler_name, original_intent):
for handler in APP_REGISTRY.get(original_intent.action, []):
if handler.__name__ == handler_name:
original_intent.activity_class = handler
navigator.startActivity(original_intent)
break
navigator.finish() # Close chooser
def onStop(self, screen):
if self.getIntent() and self.getIntent().getStringExtra("destination") == "ChooserActivity":
print("Stopped for Chooser")
else:
print("Stopped for other screen")
class ViewActivity(Activity):
def __init__(self):
super().__init__()
def onCreate(self):
screen = lv.obj()
# Get content from intent (prefer extras.url, fallback to data)
content = self.getIntent().extras.get("url", self.getIntent().data or "No content")
label = lv.label(screen)
label.set_user_data("content_label")
label.set_text(f"Viewing: {content}")
label.center()
self.setContentView(screen)
def onStart(self, screen):
content = self.getIntent().extras.get("url", self.getIntent().data or "No content")
for i in range(screen.get_child_cnt()):
if screen.get_child(i).get_user_data() == "content_label":
screen.get_child(i).set_text(f"Viewing: {content}")
def onStop(self, screen):
if self.getIntent() and self.getIntent().getStringExtra("destination") == "ViewActivity":
print("Stopped for View")
else:
print("Stopped for other screen")
class ShareActivity(Activity):
def __init__(self):
super().__init__()
def onCreate(self):
screen = lv.obj()
# Get text from intent (prefer extras.text, fallback to data)
text = self.getIntent().extras.get("text", self.getIntent().data or "No text")
label = lv.label(screen)
label.set_user_data("share_label")
label.set_text(f"Share: {text}")
label.set_pos(10, 10)
btn = lv.btn(screen)
btn.set_user_data("share_btn")
btn_label = lv.label(btn)
btn_label.set_text("Share")
btn.set_pos(10, 50)
btn.add_event_cb(lambda e: self._share_content(text), lv.EVENT.CLICKED)
self.setContentView(screen)
def _share_content(self, text):
# Dispatch to another app (e.g., MessagingActivity) or simulate sharing
print(f"Sharing: {text}") # Placeholder for actual sharing
# Example: Launch another share handler
navigator.startActivity(Intent(action="share", data=text))
navigator.finish() # Close ShareActivity
def onStop(self, screen):
if self.getIntent() and self.getIntent().getStringExtra("destination") == "ShareActivity":
print("Stopped for Share")
else:
print("Stopped for other screen")
APP_REGISTRY = { # This should be handled by a new class PackageManager:
"view": [ViewActivity], # Hypothetical activities
"share": [ShareActivity]
}