from st3m.reactor import Reactor, Responder from st3m.goose import List, Set, Optional from st3m.ui.menu import ( MenuItem, MenuItemBack, MenuItemForeground, MenuItemNoop, MenuItemAction, MenuItemLaunchPersistentView, ) from st3m.ui.elements import overlays from st3m.ui.view import View, ViewManager, ViewTransitionBlend, ViewTransitionDirection from st3m.ui.elements.menus import SimpleMenu, SunMenu from st3m.application import ( BundleManager, BundleMetadata, MenuItemAppLaunch, ApplicationContext, setup_for_app, ) from st3m.about import About from st3m import settings_menu as settings, logging, processors, wifi from st3m.ui import led_patterns import st3m.wifi import captouch, audio, leds, gc, sys_buttons, sys_display, sys_mode, media, bl00mbox import os import machine import network log = logging.Log(__name__, level=logging.INFO) #: Can be set to a bundle name that should be started instead of the main menu when run_main is called. override_main_app: Optional[str] = None def _make_reactor() -> Reactor: reactor = Reactor() def _onoff_button_swap_update() -> None: left = not settings.onoff_button_swap.value sys_buttons.configure(left) settings.onoff_button_swap.subscribe(_onoff_button_swap_update) _onoff_button_swap_update() settings.onoff_wifi.subscribe(wifi._onoff_wifi_update) wifi._onoff_wifi_update() return reactor def run_responder(r: Responder) -> None: """ Run a given Responder in the foreground, without any menu or main firmware running in the background. This is useful for debugging trivial applications from the REPL. """ reactor = _make_reactor() reactor.set_top(r) reactor.run() class ApplicationMenu(SimpleMenu): def _restore_sys_defaults(self) -> None: if ( not self.vm or not self.is_active() or self.vm.direction != ViewTransitionDirection.BACKWARD ): return # fall back to system defaults on app exit st3m.wifi._onoff_wifi_update() # set the default graphics mode, this is a no-op if # it is already set sys_display.set_mode(0) sys_display.fbconfig(240, 240, 0, 0) leds.set_slew_rate(100) leds.set_gamma(1.0, 1.0, 1.0) leds.set_auto_update(False) leds.set_brightness(settings.num_leds_brightness.value) sys_display.set_backlight(settings.num_display_brightness.value) led_patterns.set_menu_colors() # media.stop() def on_enter(self, vm: Optional[ViewManager]) -> None: super().on_enter(vm) self._restore_sys_defaults() def on_enter_done(self): # set the defaults again in case the app continued # doing stuff during the transition self._restore_sys_defaults() leds.update() def _get_bundle_menu_kinds(mgr: BundleManager) -> Set[str]: kinds: Set[str] = set() for bundle in mgr.bundles.values(): kinds.update(bundle.menu_kinds()) return kinds def _get_bundle_menu_entries(mgr: BundleManager, kind: str) -> List[MenuItem]: entries: List[MenuItem] = [] ids = sorted(mgr.bundles.keys(), key=str.lower) for id in ids: bundle = mgr.bundles[id] entries += bundle.menu_entries(kind) return entries def _make_compositor(reactor: Reactor, vm: ViewManager) -> overlays.Compositor: """ Set up top-level compositor (for combining viewmanager with overlays). """ compositor = overlays.Compositor(vm) volume = overlays.OverlayVolume() compositor.add_overlay(volume) # Tie compositor's debug overlay to setting. def _onoff_debug_update() -> None: compositor.enabled[overlays.OverlayKind.Debug] = settings.onoff_debug.value _onoff_debug_update() settings.onoff_debug.subscribe(_onoff_debug_update) # Configure debug overlay fragments. debug = overlays.OverlayDebug() debug.add_fragment(overlays.DebugReactorStats(reactor)) debug.add_fragment(overlays.DebugBattery()) compositor.add_overlay(debug) debug_touch = overlays.OverlayCaptouch() # Tie compositor's debug touch overlay to setting. def _onoff_debug_touch_update() -> None: compositor.enabled[ overlays.OverlayKind.Touch ] = settings.onoff_debug_touch.value _onoff_debug_touch_update() settings.onoff_debug_touch.subscribe(_onoff_debug_touch_update) compositor.add_overlay(debug_touch) # Tie compositor's icon visibility to setting. def _onoff_show_tray_update() -> None: compositor.enabled[ overlays.OverlayKind.Indicators ] = settings.onoff_show_tray.value _onoff_show_tray_update() settings.onoff_show_tray.subscribe(_onoff_show_tray_update) # Add icon tray. compositor.add_overlay(overlays.IconTray()) return compositor def run_view(v: View, debug_vm=True) -> None: """ Run a given View in the foreground, with an empty ViewManager underneath. This is useful for debugging simple applications from the REPL. """ reactor = _make_reactor() vm = ViewManager(ViewTransitionBlend(), debug=debug_vm) sys_mode.mode_set(2) # st3m_mode_kind_app vm.push(v) compositor = _make_compositor(reactor, vm) top = processors.ProcessorMidldeware(compositor) reactor.set_top(top) reactor.run() def run_app(klass, bundle_path=None): app_ctx = ApplicationContext(bundle_path) setup_for_app(app_ctx) run_view(klass(app_ctx), debug_vm=True) def _yeet_local_changes() -> None: os.remove("/flash/sys/.sys-installed") machine.reset() def run_main() -> None: log.info(f"starting main") log.info(f"free memory: {gc.mem_free()}") captouch.calibration_request() audio.headphones_set_volume_dB(settings.num_headphones_startup_volume_db.value) audio.speaker_set_volume_dB(settings.num_speaker_startup_volume_db.value) audio.headphones_set_minimum_volume_dB(settings.num_headphones_min_db.value) audio.speaker_set_minimum_volume_dB(settings.num_speaker_min_db.value) audio.headphones_set_maximum_volume_dB(settings.num_headphones_max_db.value) audio.speaker_set_maximum_volume_dB(settings.num_speaker_max_db.value) audio.headset_mic_set_gain_dB(settings.num_headset_mic_gain_db.value) audio.onboard_mic_set_gain_dB(settings.num_onboard_mic_gain_db.value) audio.line_in_set_gain_dB(settings.num_line_in_gain_db.value) audio.headset_mic_set_allowed(settings.onoff_headset_mic_allowed.value) audio.onboard_mic_set_allowed(settings.onoff_onboard_mic_allowed.value) audio.line_in_set_allowed(settings.onoff_line_in_allowed.value) audio.onboard_mic_to_speaker_set_allowed( settings.onoff_onboard_mic_to_speaker_allowed.value ) leds.set_brightness(settings.num_leds_brightness.value) sys_display.set_backlight(settings.num_display_brightness.value) leds.set_slew_rate(235) leds.set_gamma(1.0, 1.0, 1.0) leds.set_auto_update(False) leds.set_rgb(0, 255, 0, 0) leds.update() bundles = BundleManager() bundles.update() led_patterns.set_menu_colors() leds.set_slew_rate(20) leds.update() try: network.hostname( settings.str_hostname.value if settings.str_hostname.value else "flow3r" ) except Exception as e: log.error(f"Failed to set hostname {e}") for i in range(1, 32): bl00mbox.Channel(i).clear() bl00mbox.Channel(i).free = True menu_settings = settings.build_menu() menu_system = ApplicationMenu( [ MenuItemBack(), MenuItemLaunchPersistentView("About", About), MenuItemForeground("Settings", menu_settings), MenuItemAppLaunch(BundleMetadata("/flash/sys/apps/gr33nhouse")), MenuItemAppLaunch(BundleMetadata("/flash/sys/apps/updat3r")), MenuItemAction("Disk Mode (Flash)", machine.disk_mode_flash), MenuItemAction("Disk Mode (SD)", machine.disk_mode_sd), MenuItemAction("Yeet Local Changes", _yeet_local_changes), MenuItemAction("Reboot", machine.reset), ], ) app_kinds = _get_bundle_menu_kinds(bundles) menu_categories = ["Badge", "Music", "Media", "Apps", "Games"] for kind in app_kinds: if kind not in ["Hidden", "System"] and kind not in menu_categories: menu_categories.append(kind) categories = [ MenuItemForeground(kind, ApplicationMenu([MenuItemBack()] + entries)) for kind in menu_categories if (entries := _get_bundle_menu_entries(bundles, kind)) ] categories.append(MenuItemForeground("System", menu_system)) menu_main = SunMenu(categories) if override_main_app is not None: requested = [b for b in bundles.bundles.values() if b.name == override_main_app] if len(requested) > 1: raise Exception(f"More than one bundle named {override_main_app}") if len(requested) == 0: raise Exception(f"Requested bundle {override_main_app} not found") run_view(requested[0].load(), debug_vm=True) run_view(menu_main, debug_vm=False) __all__ = [ "run_responder", "run_view", "run_main", "run_app", ]