2025-06-13 09:02:30 +02:00
import _thread
2025-06-19 19:57:29 +02:00
import lvgl as lv
2026-01-24 20:13:50 +01:00
2025-05-12 00:05:56 +02:00
import mpos . ui
2026-01-24 19:10:51 +01:00
import mpos . ui . topmenu
2025-11-22 09:29:24 +01:00
2026-02-13 11:08:47 +01:00
from mpos import AppearanceManager , AppManager , BuildInfo , DeviceInfo , DisplayMetrics , SharedPreferences , TaskManager
2026-01-23 23:25:22 +01:00
def init_rootscreen ( ) :
""" Initialize the root screen and set display metrics. """
screen = lv . screen_active ( )
disp = screen . get_display ( )
width = disp . get_horizontal_resolution ( )
height = disp . get_vertical_resolution ( )
dpi = disp . get_dpi ( )
2026-02-13 22:47:52 +01:00
2026-01-23 23:25:22 +01:00
# Initialize DisplayMetrics with actual display values
DisplayMetrics . set_resolution ( width , height )
2026-03-11 12:38:52 +01:00
DisplayMetrics . set_dpi ( dpi )
2026-01-23 23:25:22 +01:00
print ( f " init_rootscreen set resolution to { width } x { height } at { dpi } DPI " )
2026-02-13 22:47:52 +01:00
2026-01-26 11:05:32 +01:00
# Show logo
img = lv . image ( screen )
img . set_src ( " M:builtin/res/mipmap-mdpi/MicroPythonOS-logo-white-long-w296.png " ) # from the MPOS-logo repo
2026-02-09 20:42:47 +01:00
if width < 296 :
img . set_scale ( int ( 256 * width / 296 ) )
2026-01-26 11:05:32 +01:00
img . set_blend_mode ( lv . BLEND_MODE . DIFFERENCE )
img . center ( )
2026-01-23 23:25:22 +01:00
2026-02-08 18:20:48 +01:00
def single_address_i2c_scan ( i2c_bus , address ) :
"""
Scan a specific I2C address to check if a device is present.
Args:
i2c_bus: An I2C bus object (machine.I2C instance)
address: Integer address to scan (0-127)
Returns:
True if a device responds at the specified address, False otherwise
"""
2026-02-12 14:02:11 +01:00
print ( f " Attempt to write a single byte to I2C bus address 0x { address : 02x } ... " )
2026-02-08 18:20:48 +01:00
try :
# Attempt to write a single byte to the address
# This will raise an exception if no device responds
2026-02-12 14:02:11 +01:00
i2c_bus . writeto ( address , b " " )
print ( " Write test successful " )
2026-02-08 18:20:48 +01:00
return True
2026-02-12 14:02:11 +01:00
except OSError as e :
print ( f " No device at this address: { e } " )
2026-02-08 18:20:48 +01:00
return False
except Exception as e :
# Handle any other exceptions gracefully
2026-02-12 14:02:11 +01:00
print ( f " scan error: { e } " )
2026-02-08 18:20:48 +01:00
return False
2026-02-12 14:02:11 +01:00
def fail_save_i2c ( sda , scl ) :
2026-03-22 23:03:58 +01:00
from machine import I2C , Pin
2026-02-12 14:02:11 +01:00
print ( f " Try to I2C initialized on { sda =} { scl =} " )
try :
i2c0 = I2C ( 0 , sda = Pin ( sda ) , scl = Pin ( scl ) )
except Exception as e :
2026-03-22 22:19:02 +01:00
print ( f " fail_save_i2c failed: { e } " )
2026-02-12 14:02:11 +01:00
return None
else :
2026-03-22 22:19:02 +01:00
print ( " fail_save_i2c ok " )
2026-02-12 14:02:11 +01:00
return i2c0
2026-03-22 22:19:02 +01:00
def restore_i2c ( sda , scl ) :
2026-03-22 23:03:58 +01:00
from machine import Pin
2026-03-22 22:19:02 +01:00
Pin ( sda , Pin . IN , pull = None )
Pin ( scl , Pin . IN , pull = None )
2026-02-12 14:02:11 +01:00
2026-01-23 23:25:22 +01:00
def detect_board ( ) :
import sys
if sys . platform == " linux " or sys . platform == " darwin " : # linux and macOS
return " linux "
elif sys . platform == " esp32 " :
2026-02-06 19:22:30 +01:00
2026-03-22 22:19:02 +01:00
'''
# Reading and storing all pinstates can be useful for board detection
# But reading some pins can break peripherals
# So it ' s disabled by default - it ' s more for development
try:
import mpos
from mpos.board import pinstates
mpos.pinstates = pinstates.read_all_pins(skiplist = [7,8])
except Exception as e:
print( " pinstates: WARNING: failed to read pins: " , e)
'''
2026-02-23 23:03:42 +01:00
# First do unique_id-based board detections because they're fast and don't mess with actual hardware configurations
import machine
unique_id_prefixes = machine . unique_id ( ) [ 0 : 3 ]
2026-03-11 12:38:52 +01:00
print ( " unPhone ? " )
2026-03-19 17:49:04 +01:00
if unique_id_prefixes == b ' \x30 \x30 \xf9 ' :
2026-03-11 12:38:52 +01:00
return " unphone "
2026-02-23 23:03:42 +01:00
print ( " odroid_go ? " )
2026-03-19 16:23:07 +01:00
if unique_id_prefixes == b ' \x30 \xae \xa4 ' :
2026-02-23 23:03:42 +01:00
return " odroid_go "
2026-03-22 22:19:02 +01:00
# Do I2C-based board detection
2026-03-01 22:45:30 +01:00
print ( " lilygo_t_watch_s3_plus ? " )
if i2c0 := fail_save_i2c ( sda = 10 , scl = 11 ) :
2026-03-02 00:21:52 +01:00
if single_address_i2c_scan ( i2c0 , 0x19 ) : # IMU on 0x19, vibrator on 0x5A and scan also shows: [52, 81]
2026-03-01 22:45:30 +01:00
return " lilygo_t_watch_s3_plus " # example MAC address: D0:CF:13:33:36:306
2026-03-22 22:19:02 +01:00
restore_i2c ( sda = 10 , scl = 11 )
2026-03-01 22:45:30 +01:00
2026-02-12 14:02:11 +01:00
print ( " matouch_esp32_s3_spi_ips_2_8_with_camera_ov3660 ? " )
if i2c0 := fail_save_i2c ( sda = 39 , scl = 38 ) :
2026-02-13 20:02:04 +01:00
if single_address_i2c_scan ( i2c0 , 0x14 ) or single_address_i2c_scan ( i2c0 , 0x5D ) : # "ghost" or real GT911 touch screen
2026-02-12 14:02:11 +01:00
return " matouch_esp32_s3_spi_ips_2_8_with_camera_ov3660 "
2026-03-22 22:19:02 +01:00
restore_i2c ( sda = 39 , scl = 38 ) # fix pin 39 (data0) breaking lilygo_t_display_s3's display
2026-02-08 18:20:48 +01:00
2026-02-12 14:02:11 +01:00
print ( " waveshare_esp32_s3_touch_lcd_2 ? " )
if i2c0 := fail_save_i2c ( sda = 48 , scl = 47 ) :
2026-02-13 20:02:04 +01:00
# IO48 is floating on matouch_esp32_s3_spi_ips_2_8_with_camera_ov3660 and therefore, using that for I2C will find many devices, so do this after matouch_esp32_s3_spi_ips_2_8_with_camera_ov3660
if single_address_i2c_scan ( i2c0 , 0x15 ) and single_address_i2c_scan ( i2c0 , 0x6B ) : # CST816S touch screen and IMU
2026-02-12 14:02:11 +01:00
return " waveshare_esp32_s3_touch_lcd_2 "
2026-03-22 22:19:02 +01:00
restore_i2c ( sda = 48 , scl = 47 ) # fix pin 47 (data6) and 48 (data7) breaking lilygo_t_display_s3's display
2026-02-08 19:39:28 +01:00
2026-02-13 20:02:04 +01:00
print ( " m5stack_fire ? " )
if i2c0 := fail_save_i2c ( sda = 21 , scl = 22 ) :
if single_address_i2c_scan ( i2c0 , 0x68 ) : # IMU (MPU6886)
2026-02-08 13:26:28 +01:00
return " m5stack_fire "
2026-03-22 22:19:02 +01:00
restore_i2c ( sda = 21 , scl = 22 )
2026-02-16 21:42:14 +01:00
2026-02-12 14:02:11 +01:00
print ( " fri3d_2024 ? " )
if i2c0 := fail_save_i2c ( sda = 9 , scl = 18 ) :
2026-03-23 07:49:26 +01:00
if single_address_i2c_scan ( i2c0 , 0x6A ) : # ) 0x15: CST8 touch, 0x6A: IMU
return " fri3d_2026 "
2026-02-13 20:02:04 +01:00
if single_address_i2c_scan ( i2c0 , 0x6B ) : # IMU (plus possibly the Communicator's LANA TNY at 0x38)
2026-02-12 14:02:11 +01:00
return " fri3d_2024 "
2026-03-22 22:19:02 +01:00
restore_i2c ( sda = 9 , scl = 18 )
2026-02-12 14:02:11 +01:00
2026-03-22 22:19:02 +01:00
# On devices without I2C, we use known GPIO states
2026-03-22 23:03:58 +01:00
from machine import Pin
2026-03-22 22:19:02 +01:00
print ( " (emulated) lilygo_t_display_s3 ? " )
try :
# 2 buttons have PCB pull-ups so they'll be high unless pressed
pin0 = Pin ( 0 , Pin . IN )
pin14 = Pin ( 14 , Pin . IN )
if pin0 . value ( ) == 1 and pin14 . value ( ) == 1 :
return " lilygo_t_display_s3 " # display gets confused by the i2c stuff below
except Exception as e :
print ( f " lilygo_t_display_s3 detection got exception: { e } " )
2026-02-23 23:03:42 +01:00
2026-02-20 15:39:09 +01:00
print ( " Unknown board: couldn ' t detect known I2C devices or unique_id prefix " )
2026-02-16 21:42:14 +01:00
2026-02-13 11:08:47 +01:00
# EXECUTION STARTS HERE
print ( f " MicroPythonOS { BuildInfo . version . release } running lib/mpos/main.py " )
2026-01-23 23:25:22 +01:00
board = detect_board ( )
2026-02-20 15:39:09 +01:00
if board :
print ( f " Detected { board } system, importing mpos.board. { board } " )
DeviceInfo . set_hardware_id ( board )
__import__ ( f " mpos.board. { board } " )
2025-11-22 08:24:28 +01:00
# Allow LVGL M:/path/to/file or M:relative/path/to/file to work for image set_src etc
import mpos . fs_driver
fs_drv = lv . fs_drv_t ( )
mpos . fs_driver . fs_register ( fs_drv , ' M ' )
2026-01-24 22:11:09 +01:00
# Needed to load the logo from storage:
try :
import freezefs_mount_builtin
except Exception as e :
# This will throw an exception if there is already a "/builtin" folder present
print ( " main.py: WARNING: could not import/run freezefs_mount_builtin: " , e )
2026-03-18 12:01:26 +01:00
prefs = SharedPreferences ( " com.micropythonos.settings " ) # if not value is set, it will start the HowTo app
2025-06-17 10:45:29 +02:00
2026-01-23 22:26:01 +01:00
AppearanceManager . init ( prefs )
2026-01-24 22:11:09 +01:00
init_rootscreen ( ) # shows the boot logo
2025-07-04 15:50:51 +02:00
mpos . ui . topmenu . create_notification_bar ( )
2026-01-23 23:25:22 +01:00
mpos . ui . topmenu . create_drawer ( )
2025-05-25 11:36:49 +02:00
mpos . ui . handle_back_swipe ( )
mpos . ui . handle_top_swipe ( )
2025-10-30 21:22:37 +01:00
2025-10-16 12:29:44 +02:00
# Clear top menu, notification bar, swipe back and swipe down buttons
# Ideally, these would be stored in a different focusgroup that is used when the user opens the drawer
focusgroup = lv . group_get_default ( )
if focusgroup : # on esp32 this may not be set
focusgroup . remove_all_objs ( ) # might be better to save and restore the group for "back" actions
2025-10-30 21:22:37 +01:00
2026-01-09 12:03:33 +01:00
# Custom exception handler that does not deinit() the TaskHandler because then the UI hangs:
2025-11-07 11:58:53 +01:00
def custom_exception_handler ( e ) :
2026-01-09 12:03:33 +01:00
print ( f " TaskHandler ' s custom_exception_handler called: { e } " )
import sys
sys . print_exception ( e ) # NOQA
# No need to deinit() and re-init LVGL:
#mpos.ui.task_handler.deinit() # default task handler does this, but then things hang
2026-02-24 00:10:09 +01:00
#focusgroup = lv.group_get_default()
#if focusgroup: # on esp32 this may not be set
# otherwise it does focus_next and then crashes while doing lv.deinit()
#focusgroup.remove_all_objs()
#focusgroup.delete()
2026-01-09 12:03:33 +01:00
#lv.deinit()
2025-11-07 11:58:53 +01:00
2026-02-27 14:11:56 +01:00
import task_handler
2026-03-11 18:44:06 +01:00
# 5ms is recommended for MicroPython+LVGL on desktop (less results in lower framerate but still okay)
# 1ms gives highest framerate on esp32-s3's but might have side effects?
mpos . ui . task_handler = task_handler . TaskHandler ( duration = 1 , exception_hook = custom_exception_handler )
2026-01-09 12:03:33 +01:00
# Convenient for apps to be able to access these:
mpos . ui . task_handler . TASK_HANDLER_STARTED = task_handler . TASK_HANDLER_STARTED
mpos . ui . task_handler . TASK_HANDLER_FINISHED = task_handler . TASK_HANDLER_FINISHED
2025-04-21 11:37:30 +02:00
2025-06-13 09:02:30 +02:00
try :
2025-11-18 13:19:10 +01:00
from mpos . net . wifi_service import WifiService
2026-01-24 19:10:51 +01:00
_thread . stack_size ( TaskManager . good_stack_size ( ) )
2025-11-18 13:19:10 +01:00
_thread . start_new_thread ( WifiService . auto_connect , ( ) )
2025-06-13 09:02:30 +02:00
except Exception as e :
2025-11-18 13:19:10 +01:00
print ( f " Couldn ' t start WifiService.auto_connect thread because: { e } " )
2025-05-20 21:29:24 +02:00
2026-02-13 11:08:47 +01:00
# Start launcher first so it's always at bottom of stack
2026-01-25 00:19:38 +01:00
launcher_app = AppManager . get_launcher ( )
started_launcher = AppManager . start_app ( launcher_app . fullname )
2026-03-11 21:14:04 +01:00
# Then start auto_start_app_early if configured
2026-03-18 12:01:26 +01:00
auto_start_app_early = prefs . get_string ( " auto_start_app_early " , " com.micropythonos.howto " )
2026-03-11 21:14:04 +01:00
if auto_start_app_early and launcher_app . fullname != auto_start_app_early :
result = AppManager . start_app ( auto_start_app_early )
2025-12-11 22:07:04 +01:00
if result is not True :
2026-03-11 21:14:04 +01:00
print ( f " WARNING: could not run { auto_start_app_early } app " )
else : # if no auto_start_app_early was configured (this could be improved to start it *after* auto_start_app_early finishes)
auto_start_app = prefs . get_string ( " auto_start_app " , None )
if auto_start_app and launcher_app . fullname != auto_start_app and auto_start_app_early != auto_start_app :
result = AppManager . start_app ( auto_start_app )
if result is not True :
print ( f " WARNING: could not run { auto_start_app } app " )
2025-05-01 15:44:12 +02:00
2025-12-11 19:02:19 +01:00
# Create limited aiorepl because it's better than nothing:
import aiorepl
2025-12-11 19:29:50 +01:00
async def asyncio_repl ( ) :
2025-12-11 21:11:59 +01:00
print ( " Starting very limited asyncio REPL task. To stop all asyncio tasks and go to real REPL, do: import mpos ; mpos.TaskManager.stop() " )
2025-12-11 19:29:50 +01:00
await aiorepl . task ( )
2026-01-24 20:13:50 +01:00
TaskManager . create_task ( asyncio_repl ( ) ) # only gets started after TaskManager.start()
2025-12-11 19:02:19 +01:00
2026-02-25 19:49:30 +01:00
try :
2026-03-17 19:30:34 +01:00
from mpos import WebServer
WebServer . auto_start ( )
2026-02-25 19:49:30 +01:00
except Exception as e :
2026-03-17 19:30:34 +01:00
print ( f " Could not start webserver - this is normal on desktop systems: { e } " )
2026-02-25 19:49:30 +01:00
2025-12-11 19:02:19 +01:00
async def ota_rollback_cancel ( ) :
2025-11-12 12:16:12 +01:00
try :
2026-01-21 12:16:27 +01:00
from esp32 import Partition
Partition . mark_app_valid_cancel_rollback ( )
2025-11-12 12:16:12 +01:00
except Exception as e :
print ( " main.py: warning: could not mark this update as valid: " , e )
2025-12-11 19:02:19 +01:00
if not started_launcher :
print ( f " WARNING: launcher { launcher_app } failed to start, not cancelling OTA update rollback " )
else :
2026-01-24 20:13:50 +01:00
TaskManager . create_task ( ota_rollback_cancel ( ) ) # only gets started after TaskManager.start()
2025-12-11 19:02:19 +01:00
2025-12-11 21:01:29 +01:00
try :
2026-01-24 20:13:50 +01:00
TaskManager . start ( ) # do this at the end because it doesn't return
2025-12-11 21:01:29 +01:00
except KeyboardInterrupt as k :
2026-01-24 20:13:50 +01:00
print ( f " TaskManager.start() got KeyboardInterrupt, falling back to REPL shell... " ) # only works if no aiorepl is running
2025-12-11 21:01:29 +01:00
except Exception as e :
2026-01-24 20:13:50 +01:00
print ( f " TaskManager.start() got exception: { e } " )