This file provides guidance to agents when working with code in this repository.
- Build is driven by
./scripts/build_mpos.sh <target>; it mutates tracked files (patcheslvgl_micropython/lib/micropython/ports/esp32/main/idf_component.yml, appends include tomicropython-camera-API/src/manifest.py, and toggles@micropython.viperininternal_filesystem/lib/mpos/audio/stream_wav.py). Re-run builds expecting these edits to persist unless reverted. - Unix/macOS builds rely on symlinks created by
build_mpos.shinlvgl_micropython/ext_mod/forc_mposandsecp256k1-embedded-ecdhbecauseUSER_C_MODULEis unreliable on those targets. - Syntax tests run via
./tests/syntax.shand compile everyinternal_filesystem/**/*.pywithmpy-cross; failing files are reported by path. - Unit tests run via
./tests/unittest.sh [test_file] [--ondevice]; runner injectsmain.pyand disablesmpos.TaskManagerfor desktop, but on-device runs must NOT re-run boot/main (the script handles this). - Graphical tests are detected by filename containing
graphicaland run with LVGL boot/main injected; non-graphical tests run without boot files. - To run a single test, pass a file path to
./tests/unittest.sh(absolute path is resolved inside the script). - Testing workflow details and examples live in
tests/README.md; check it before adding new tests. - Code formatting for Python in this repo is ruff with double quotes configured in
ruff.toml(quote-style = "double").
Guidelines:
- If something is incomplete or lacks functionality that is needed to finish the task, then implement the missing functionality, rather than working around it.
- Always add a timeout -s 9 30 to ./scripts/run_desktop.sh so run: timeout -s 9 30 ./scripts/run_desktop.sh
Guidelines for writing or updating tests:
- Use the testing facilities in ./internal_filesystem/lib/mpos/ui/testing.py and feel free to add new ones there, NOT ad hoc in the test itself.
- When adding graphical tests, follow the helpers and conventions described in tests/README.md.
LVGL tips:
- import lvgl as
lvand uselv.to access it lv.screen_active()(notlv.scr_act())- use
buttoninstead ofbtn,imageinstead ofimg - use
lv.EVENT.VALUE_CHANGEDinstead oflv.EVENT_VALUE_CHANGED - instead of
lv.OBJ_FLAG.CLICKABLE, uselv.obj.FLAG.CLICKABLE(same pattern for other flags) - instead of
.set_hidden(True)use.add_flag(lv.obj.FLAG.HIDDEN); instead of.set_hidden(False)use.remove_flag(lv.obj.FLAG.HIDDEN) - use
.remove_flag()instead of.clear_flag() - use
obj.remove_state(...)notobj.clear_state(...) - event handlers need 3 arguments:
button.add_event_cb(button_cb, lv.EVENT.CLICKED, None) - if you pass a method as an event callback, it must accept the event argument:
def callback(self, event). Using the same method as both a direct call and an event callback requires a default:def method(self, event=None). - don't hard-code display resolution; use
lv.pct(100)or other techniques to scale the interface DRAW_PART_BEGINdoes not exist anymore- don't use
get_child_by_type(); use a global variable with the child you want instead - msgbox:
msgbox = lv.msgbox()thenmsgbox.add_title("title") - use
lv.buttonmatrix.CTRL.CHECKABLEinstead oflv.BUTTONMATRIX_CTRL_CHECKABLE - use
lv.buttonmatrix.CTRL.CHECKEDinstead oflv.BUTTONMATRIX_CTRL_CHECKED - colors:
RED = lv.palette_main(lv.PALETTE.RED)orDARKPINK = lv.color_hex(0xEC048C) - use
lv.anim_t.path_ease_in_outnotlv.anim_path_ease_in_out - instead of
label.set_long_mode(lv.label.LONG.WRAP)uselabel.set_long_mode(lv.label.LONG_MODE.WRAP) - use
style_obj = lv.style_t()thenstyle_obj.init()instead oflv.style() - always call
style.init()afterlv.style_t()before calling setters likeset_bg_color()— without it the device may hang - In LVGL 9.x style setters take only the value (no selector). The selector goes in
add_style(). E.g.style.set_bg_color(lv.color_hex(0x...))thenobj.add_style(style, lv.PART.ITEMS | lv.STATE.CHECKED). lv.buttonmatrixhas noset_button_text()orset_button_ctrl()in this binding. To update text, rebuild and callset_map(). To mark buttons visually (e.g. solved state), change the text symbol itself (e.g. append "!").lv.buttonmatrix.set_map()firesLV_EVENT_VALUE_CHANGEDasynchronously (next LVGL tick), causing phantom second-selection events. Guard with a time-based debounce (time.ticks_diff(now, last_ts) < 50) rather than a simple flag.- LVGL object wrappers (e.g.
lv.button(),lv.obj()) do NOT support arbitrary Python attribute assignment (btn.idx = 5raisesAttributeError). To associate data with a widget, use closures/lambdas (lambda e, i=idx: callback(e, i)) or maintain parallel lists keyed by list index. - In event callbacks, use
event.get_target_obj()instead ofevent.get_current_target(). The latter returns a genericBlobthat can hang when passed to typed LVGL methods (e.g.lv.list.get_button_text()).get_target_obj()returns a properly typedlv.obj.
MicroPythonOS tips:
self.appFullNameis automatically set by the ActivityNavigator when launching an Activity. Use it instead of hard-coding the app's package name (e.g. forSharedPreferences(self.appFullName)).
MicroPython compatibility:
- Some builds ship a minimal
randommodule withoutrandom.Randomorrandom.shuffle. For shuffling, implement Fisher-Yates manually withrandom.randint. - For deterministic jitter in apps, prefer a tiny local LCG (linear congruential generator) instead of
random.Random.