Files
MicroPythonOS/internal_filesystem/lib/mpos/main.py
T
Jens Diemer 0423e09522 Updates for ODROID-GO (#40)
Setup the "Buzzer" and play intro and outro ;) Don't know if "I2S audio" is possible.

Battery "settings": I tested to run ODROID-GO as long as it's possible. The min. raw ADC value on
ODROID-GO i have seen is 210. So update the calculation.

Fix the boot by moving ODROID-GO below `fri3d_2024` because the device will hard crash on
`fail_save_i2c(sda=9, scl=18)` like:
```
MicroPythonOS 0.8.1 running lib/mpos/main.py
matouch_esp32_s3_spi_ips_2_8_with_camera_ov3660 ?
Try to I2C initialized on sda=39 scl=38
OK
Attempt to write a single byte to I2C bus address 0x14...
No device at this address: [Errno 116] ETIMEDOUT
Attempt to write a single byte to I2C bus address 0x5d...
No device at this address: [Errno 116] ETIMEDOUT
waveshare_esp32_s3_touch_lcd_2 ?
Try to I2C initialized on sda=48 scl=47
Failed: invalid pin
m5stack_fire ?
Try to I2C initialized on sda=21 scl=22
OK
Attempt to write a single byte to I2C bus address 0x68...
No device at this address: [Errno 19] ENODEV
fri3d_2024 ?
Try to I2C initialized on sda=9 scl=18
OK

A fatal error occurred. The crash dump printed below may be used to help
determine what caused it. If you are not already running the most recent
version of MicroPython, consider upgrading. New versions often fix bugs.

To learn more about how to debug and/or report this crash visit the wiki
page at: https://github.com/micropython/micropython/wiki/ESP32-debugging

LVGL MicroPython
IDF version : v5.4
Machine     : Generic ESP32 module with SPIRAM with ESP32

Guru Meditation Error: Core  1 panic'ed (LoadProhibited). Exception was unhandled.

Core  1 register dump:
PC      : 0x401b04dd  PS      : 0x00060830  A0      : 0x801b0944  A1      : 0x3ffdb390
A2      : 0x3f80d2b0  A3      : 0x00000054  A4      : 0x3f8105e8  A5      : 0x3f54b240
A6      : 0x00000001  A7      : 0xaaaaae2a  A8      : 0x00000019  A9      : 0x3ffdb370
A10     : 0xaaaaae2a  A11     : 0x00000063  A12     : 0x3ffc7ccc  A13     : 0x00000000
A14     : 0x3f4464f4  A15     : 0x00000001  SAR     : 0x00000020  EXCCAUSE: 0x0000001c
EXCVADDR: 0xaaaaae37  LBEG    : 0x401d2964  LEND    : 0x401d296d  LCOUNT  : 0x00000000

Backtrace: 0x401b04da:0x3ffdb390 0x401b0941:0x3ffdb3b0 0x40086719:0x3ffdb3d0 0x401a90da:0x3ffdb460
0x401b07ba:0x3ffdb490 0x40085de9:0x3ffdb4b0 0x401a90da:0x3ffdb540 0x401b07ba:0x3ffdb5b0
0x40085de9:0x3ffdb5d0 0x401a90da:0x3ffdb660 0x401b07ba:0x3ffdb690 0x401b083a:0x3ffdb6b0
0x401d35c1:0x3ffdb6f0 0x401d3809:0x3ffdb730 0x401b0919:0x3ffdb830 0x40085b59:0x3ffdb870
0x401a90da:0x3ffdb900 0x401b07ba:0x3ffdb970 0x401b07e2:0x3ffdb990 0x401e8d02:0x3ffdb9b0
0x401e90c9:0x3ffdba40 0x401c5b2d:0x3ffdba70
```
2026-02-16 21:42:14 +01:00

229 lines
8.5 KiB
Python

import task_handler
import _thread
import lvgl as lv
import mpos.ui
import mpos.ui.topmenu
from mpos import AppearanceManager, AppManager, BuildInfo, DeviceInfo, DisplayMetrics, SharedPreferences, TaskManager
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()
# Initialize DisplayMetrics with actual display values
DisplayMetrics.set_resolution(width, height)
DisplayMetrics.set_dpi(dpi)
print(f"init_rootscreen set resolution to {width}x{height} at {dpi} DPI")
# 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
if width < 296:
img.set_scale(int(256 * width/296))
img.set_blend_mode(lv.BLEND_MODE.DIFFERENCE)
img.center()
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
"""
print(f"Attempt to write a single byte to I2C bus address 0x{address:02x}...")
try:
# Attempt to write a single byte to the address
# This will raise an exception if no device responds
i2c_bus.writeto(address, b"")
print("Write test successful")
return True
except OSError as e:
print(f"No device at this address: {e}")
return False
except Exception as e:
# Handle any other exceptions gracefully
print(f"scan error: {e}")
return False
def fail_save_i2c(sda, scl):
from machine import I2C, Pin
print(f"Try to I2C initialized on {sda=} {scl=}")
try:
i2c0 = I2C(0, sda=Pin(sda), scl=Pin(scl))
except Exception as e:
print(f"Failed: {e}")
return None
else:
print("OK")
return i2c0
def check_pins(*pins):
from machine import Pin
print(f"Test {pins=}...")
for pin in pins:
try:
Pin(pin)
except Exception as e:
print(f"Failed to initialize {pin=}: {e}")
return True
print("All pins initialized successfully")
return True
def detect_board():
import sys
if sys.platform == "linux" or sys.platform == "darwin": # linux and macOS
return "linux"
elif sys.platform == "esp32":
print("matouch_esp32_s3_spi_ips_2_8_with_camera_ov3660 ?")
if i2c0 := fail_save_i2c(sda=39, scl=38):
if single_address_i2c_scan(i2c0, 0x14) or single_address_i2c_scan(i2c0, 0x5D): # "ghost" or real GT911 touch screen
return "matouch_esp32_s3_spi_ips_2_8_with_camera_ov3660"
print("waveshare_esp32_s3_touch_lcd_2 ?")
if i2c0 := fail_save_i2c(sda=48, scl=47):
# 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
return "waveshare_esp32_s3_touch_lcd_2"
print("m5stack_fire ?")
if i2c0 := fail_save_i2c(sda=21, scl=22):
if single_address_i2c_scan(i2c0, 0x68): # IMU (MPU6886)
return "m5stack_fire"
import machine
unique_id_prefix = machine.unique_id()[0]
print("odroid_go ?")
if unique_id_prefix == 0x30:
return "odroid_go"
print("fri3d_2024 ?")
if i2c0 := fail_save_i2c(sda=9, scl=18):
if single_address_i2c_scan(i2c0, 0x6B): # IMU (plus possibly the Communicator's LANA TNY at 0x38)
return "fri3d_2024"
print("fri3d_2026 ?")
if unique_id_prefix == 0xDC: # prototype board had: dc:b4:d9:0b:7d:80
# or: if single_address_i2c_scan(i2c0, 0x6A): # IMU currently not installed on prototype board
return "fri3d_2026"
raise Exception(
"Unknown ESP32-S3 board: couldn't detect known I2C devices or unique_id prefix"
)
# EXECUTION STARTS HERE
print(f"MicroPythonOS {BuildInfo.version.release} running lib/mpos/main.py")
board = detect_board()
print(f"Detected {board} system, importing mpos.board.{board}")
DeviceInfo.set_hardware_id(board)
__import__(f"mpos.board.{board}")
# 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')
# 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)
prefs = SharedPreferences("com.micropythonos.settings")
AppearanceManager.init(prefs)
init_rootscreen() # shows the boot logo
mpos.ui.topmenu.create_notification_bar()
mpos.ui.topmenu.create_drawer()
mpos.ui.handle_back_swipe()
mpos.ui.handle_top_swipe()
# 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
# Custom exception handler that does not deinit() the TaskHandler because then the UI hangs:
def custom_exception_handler(e):
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
# otherwise it does focus_next and then crashes while doing lv.deinit()
#focusgroup.remove_all_objs()
#focusgroup.delete()
#lv.deinit()
import sys
if sys.platform == "esp32":
mpos.ui.task_handler = task_handler.TaskHandler(duration=5, exception_hook=custom_exception_handler) # 1ms gives highest framerate on esp32-s3's but might have side effects?
else:
mpos.ui.task_handler = task_handler.TaskHandler(duration=5, exception_hook=custom_exception_handler) # 5ms is recommended for MicroPython+LVGL on desktop (less results in lower framerate)
# 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
try:
from mpos.net.wifi_service import WifiService
_thread.stack_size(TaskManager.good_stack_size())
_thread.start_new_thread(WifiService.auto_connect, ())
except Exception as e:
print(f"Couldn't start WifiService.auto_connect thread because: {e}")
# Start launcher first so it's always at bottom of stack
launcher_app = AppManager.get_launcher()
started_launcher = AppManager.start_app(launcher_app.fullname)
# Then start auto_start_app if configured
auto_start_app = prefs.get_string("auto_start_app", None)
if auto_start_app and launcher_app.fullname != 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")
# Create limited aiorepl because it's better than nothing:
import aiorepl
async def asyncio_repl():
print("Starting very limited asyncio REPL task. To stop all asyncio tasks and go to real REPL, do: import mpos ; mpos.TaskManager.stop()")
await aiorepl.task()
TaskManager.create_task(asyncio_repl()) # only gets started after TaskManager.start()
async def ota_rollback_cancel():
try:
from esp32 import Partition
Partition.mark_app_valid_cancel_rollback()
except Exception as e:
print("main.py: warning: could not mark this update as valid:", e)
if not started_launcher:
print(f"WARNING: launcher {launcher_app} failed to start, not cancelling OTA update rollback")
else:
TaskManager.create_task(ota_rollback_cancel()) # only gets started after TaskManager.start()
try:
TaskManager.start() # do this at the end because it doesn't return
except KeyboardInterrupt as k:
print(f"TaskManager.start() got KeyboardInterrupt, falling back to REPL shell...") # only works if no aiorepl is running
except Exception as e:
print(f"TaskManager.start() got exception: {e}")