Date: 2024-12-19
When MicroPython firmware fails during boot (e.g., due to import errors in frozen modules), the board may appear dead with no visible error output. Debug builds have REPL enabled, but if the main script crashes on import, you won't see the stacktrace unless you manually trigger the import.
Use disco repl import to manually import the boot module and see any stacktraces.
-
Build debug firmware
make debug
-
Flash to board
scripts/disco flash program bin/debug.bin --addr 0x08000000
-
Test boot sequence
scripts/disco repl import
This will:
- Stop OpenOCD if running
- Wait for USB CDC device
- Import
hardwaretest(the default boot module) - Show any stacktraces
-
Analyze error - Example output:
Traceback (most recent call last): File "<stdin>", line 1, in <module> File "hardwaretest.py", line 1, in <module> File "gui/__init__.py", line 1, in <module> File "gui/components/__init__.py", line 3, in <module> File "gui/components/keyboard.py", line 7, in <module> AttributeError: 'module' object has no attribute 'btnm' -
Fix the issue in source code
-
Repeat from step 1 until no errors
You can test individual modules to isolate issues:
# Test just the GUI module
scripts/disco repl import gui
# Test specific component
scripts/disco repl import gui.components.qrcode
# Test with longer timeout for slow imports
scripts/disco repl import hardwaretest -t 30Understanding the import chain helps locate issues:
boot.py
└── pyb.main("hardwaretest.py")
└── hardwaretest.py
└── gui/__init__.py
└── gui/core.py
└── gui/common.py
└── gui/components/__init__.py
├── gui/components/qrcode.py
├── gui/components/keyboard.py
└── ...
When upgrading LVGL versions, many API changes cause boot failures:
Error:
AttributeError: 'lv_style_t' object has no attribute 'body'
Fix: LVGL 8.x replaced style.body.main_color with style.set_bg_color():
# Old (LVGL 7.x)
qr_style.body.main_color = lv.color_hex(0xFFFFFF)
# New (LVGL 8.x)
qr_style.init()
qr_style.set_bg_color(lv.color_hex(0xFFFFFF))Error:
AttributeError: 'module' object has no attribute 'btnm'
Fix: lv.btnm renamed to lv.buttonmatrix:
# Old
class HintKeyboard(lv.btnm):
# New
class HintKeyboard(lv.buttonmatrix):- Check LVGL API: Use
scripts/disco repl exec "import lvgl as lv; print(dir(lv))"to see available classes - Check style methods:
scripts/disco repl exec "import lvgl as lv; s = lv.style_t(); print(dir(s))" - Incremental testing: Fix one error at a time, rebuild, and test again
- Keep REPL alive: Debug builds keep REPL active even after import failures, allowing further investigation
⏺ REPL not responding. Let me reset and capture boot output:
⏺ Bash(scripts/disco serial boot 10) timeout: 20s
⎿ Error: Exit code 1
Error: OpenOCD not running. Run 'disco connect' first.
⎿ Interrupted · What should Claude do instead?
⏺ You're right. I used scripts/disco serial repl 5 which worked. Let me try that again:
⏺ Bash(scripts/disco serial repl 5) timeout: 15s
⎿ === REPL Test (5s timeout) ===
Device: /dev/tty.usbmodem335D375F33382
Sending wake-up sequence...
… +2 lines (ctrl+o to expand)
⏺ No response now. Board may be stuck. Let me reset via OpenOCD:
⏺ Bash(scripts/disco ocd connect && scripts/disco cpu reset) timeout: 30s
⎿ Starting OpenOCD...
OpenOCD connected successfully
Target info:
… +3 lines (ctrl+o to expand)
⏺ Bash(scripts/disco cpu resume)
⎿ CPU resumed
⏺ Bash(scripts/disco ocd disconnect; sleep 5; scripts/disco serial repl 5) timeout: 20s
⎿ OpenOCD stopped
=== REPL Test (5s timeout) ===
Device: /dev/tty.usbmodem335D375F33382
… +15 lines (ctrl+o to expand)
⏺ Found a stacktrace!
File "gui/components/qrcode.py", line 12, in
AttributeError: 'lv_style_t' object has no attribute 'body'
LVGL style API issue - body attribute doesn't exist in this version. Let me explore more:
⏺ Bash(scripts/disco repl exec "import os; print(os.listdir('/flash'))") timeout: 15s
⎿ ['boot.py', 'main.py', 'pybcdc.inf', 'README.txt', '.fseventsd']
⏺ Read(src/gui/components/qrcode.py)
⎿ Read 308 lines
⏺ Found the issue in src/gui/components/qrcode.py:12:
qr_style.body.main_color = lv.color_hex(0xFFFFFF) # LVGL 7.x API
The body attribute was removed in LVGL 8.x. Let me check the flash and try running hardwaretest.py:
⏺ Bash(scripts/disco repl exec "import platform; print('version:', timeout:
platform.version)") 15s
⎿ version: version:tag100100900001</version:tag10>
⏺ Bash(scripts/disco repl exec "import hardwaretest") timeout: 15s
⎿ Traceback (most recent call last):
File "", line 1, in
File "hardwaretest.py", line 1, in
… +6 lines (ctrl+o to expand)
⏺ Confirmed! The import chain fails at qrcode.py:12. Let me check LVGL version:
⏺ Bash(scripts/disco repl exec "import lvgl as lv; print('LVGL version:', timeout:
lv.version_info())") 15s
⎿ LVGL version:
⏺ Bash(scripts/disco repl exec "import lvgl as lv; s = lv.style_t(); print(dir(s))") timeout: 15s
⎿ ['class', 'copy', 'SIZE', 'cast', 'cast_instance', 'dereference', 'ge
t_prop', 'get_prop_inlined', 'has_group', 'init', 'is_const', 'is_empty', 'prop_cnt', 're
move_prop', 'reset', 'set_align', 'set_anim', 'set_anim_duration', 'set_arc_color', 'set_
… +28 lines (ctrl+o to expand)
⏺ Summary
LVGL API mismatch! The firmware has LVGL 8.x/9.x but qrcode.py uses LVGL 7.x style API.
Old API (LVGL 7.x):
qr_style.body.main_color = lv.color_hex(0xFFFFFF)
qr_style.text.color = lv.color_hex(0)
New API (LVGL 8.x+):
qr_style.set_bg_color(lv.color_hex(0xFFFFFF))
qr_style.set_text_color(lv.color_hex(0))
The boot sequence is:
To manually test boot.py logic:
exec(open('/flash/boot.py').read()) # or just import the modules
Want me to fix qrcode.py to use the new LVGL 8.x style API?
⏺ Read(src/gui/components/qrcode.py)
⎿ Read 308 lines
⏺ Let me update the file to LVGL 8.x API:
⏺ Update(src/gui/components/qrcode.py)
⎿ Added 16 lines, removed 8 lines
9 from qrencoder import QREncoder
10
11 qr_style = lv.style_t()
12 -qr_style.body.main_color = lv.color_hex(0xFFFFFF)
13 -qr_style.body.grad_color = lv.color_hex(0xFFFFFF)
14 -qr_style.body.opa = 255
15 -qr_style.text.opa = 255
16 -qr_style.text.color = lv.color_hex(0)
17 -qr_style.text.line_space = 0
18 -qr_style.text.letter_space = 0
19 -qr_style.body.radius = 10
12 +qr_style.init()
13 +qr_style.set_bg_color(lv.color_hex(0xFFFFFF))
14 +qr_style.set_bg_grad_color(lv.color_hex(0xFFFFFF))
15 +qr_style.set_bg_opa(255)
16 +qr_style.set_text_opa(255)
17 +qr_style.set_text_color(lv.color_hex(0))
18 +qr_style.set_text_line_space(0)
19 +qr_style.set_text_letter_space(0)
20 +qr_style.set_radius(10)
21
22 +# Transparent style (replacement for lv.style_transp_tight)
23 +style_transp = lv.style_t()
24 +style_transp.init()
25 +style_transp.set_bg_opa(0)
26 +style_transp.set_border_width(0)
27 +style_transp.set_pad_all(0)
28 +
29 QR_SIZES = [17, 32, 53, 78, 106, 154, 192, 230, 271, 367, 458, 586, 718, 858]
30 BTNSIZE = 70
31
⏺ Update(src/gui/components/qrcode.py)
⎿ Added 5 lines, removed 3 lines
39 def init(self, *args, **kwargs):
40 super().init(*args, **kwargs)
41 style = lv.style_t()
42 - lv.style_copy(style, qr_style)
43 - style.text.font = lv.font_roboto_16
44 - style.text.color = lv.color_hex(0x192432)
42 + style.init()
43 + style.set_bg_color(lv.color_hex(0xFFFFFF))
44 + style.set_bg_opa(255)
45 + style.set_text_font(lv.font_roboto_16)
46 + style.set_text_color(lv.color_hex(0x192432))
47
48 self.encoder = None
49 self._autoplay = True
⏺ Update(src/gui/components/qrcode.py)
⎿ Added 2 lines, removed 2 lines
59 self.create_playback_controls(style)
60
61 self.note = lv.label(self)
62 - self.note.set_style(0, style)
62 + self.note.add_style(style, 0)
63 self.note.set_text("")
64 - self.note.set_align(lv.label.ALIGN.CENTER)
64 + self.note.set_style_text_align(lv.TEXT_ALIGN.CENTER, 0)
65
66 self.set_text(self._text)
67 self.task = asyncio.create_task(self.animate())
⏺ Update(src/gui/components/qrcode.py)
⎿ Added 4 lines, removed 3 lines
75
76 @spacing.setter
77 def spacing(self, spacing):
78 - qr_style = lv.style_t()
79 - qr_style.body.border.width = spacing
80 - self.qr.set_style(0, qr_style)
78 + sp_style = lv.style_t()
79 + sp_style.init()
80 + sp_style.set_border_width(spacing)
81 + self.qr.add_style(sp_style, 0)
82 self._spacing = spacing
83
84 def create_playback_controls(self, style):
⏺ Update(src/gui/components/qrcode.py)
⎿ Added 1 line, removed 1 line
83
84 def create_playback_controls(self, style):
85 self.playback = lv.obj(self)
86 - self.playback.set_style(lv.style_transp_tight)
86 + self.playback.add_style(style_transp, 0)
87 self.playback.set_size(480, BTNSIZE)
88 self.playback.set_y(640)
89
⏺ Update(src/gui/components/qrcode.py)
⎿ Added 1 line, removed 1 line
127
128 def create_density_controls(self, style):
129 self.controls = lv.obj(self)
130 - self.controls.set_style(lv.style_transp_tight)
130 + self.controls.add_style(style_transp, 0)
131 self.controls.set_size(480, BTNSIZE)
132 self.controls.set_y(740)
133 plus = lv.btn(self.controls)
⏺ Update(src/gui/components/qrcode.py)
⎿ Added 2 lines, removed 2 lines
146
147 lbl = lv.label(self.controls)
148 lbl.set_text("QR code density")
149 - lbl.set_style(0, style)
150 - lbl.set_align(lv.label.ALIGN.CENTER)
149 + lbl.add_style(style, 0)
150 + lbl.set_style_text_align(lv.TEXT_ALIGN.CENTER, 0)
151 lbl.align(self.controls, lv.ALIGN.CENTER, 0, 0)
152
153 self.controls.set_hidden(True)
⏺ Update(src/gui/components/qrcode.py)
⎿ Added 1 line, removed 1 line
300 def _set_text(self, text):
301 # one bcur frame doesn't require checksum
302 print(text)
303 - self.set_style(qr_style)
303 + self.add_style(qr_style, 0)
304 self.qr.set_text(text)
305 self.qr.align(self, lv.ALIGN.CENTER, 0, -100 if self.is_fullscreen else
0)
306 self.note.align(self, lv.ALIGN.IN_BOTTOM_MID, 0, 0)
⏺ Now let me rebuild and test:
⏺ Bash(make debug 2>&1 | tail -30)
⎿ MPY apps/label.py
MPY apps/bip85.py
MPY apps/blindingkeys/init.py
… +27 lines (ctrl+o to expand)
⏺ Build succeeded. Flashing:
⏺ Bash(scripts/disco ocd connect && scripts/disco flash program bin/debug.bin --addr timeout: 3m
0x08000000) 0s
⎿ Starting OpenOCD...
OpenOCD connected successfully
Target info:
… +27 lines (ctrl+o to expand)
⏺ Bash(scripts/disco ocd disconnect; sleep 5; scripts/disco serial repl 5) timeout: 20s
⎿ OpenOCD stopped
=== REPL Test (5s timeout) ===
Warning: USB OTG (miniUSB) not detected!
… +6 lines (ctrl+o to expand)
⏺ Bash(sleep 5; scripts/disco serial list)
⎿ === Serial Devices ===
USB Modem devices (tty.*):
/dev/tty.usbmodem21403
… +4 lines (ctrl+o to expand)
⏺ Bash(scripts/disco ocd connect && scripts/disco cpu reset && scripts/disco cpu resume timeout:
&& scripts/disco ocd disconnect) 30s
⎿ Starting OpenOCD...
OpenOCD connected successfully
Target info:
… +5 lines (ctrl+o to expand)
⏺ Bash(sleep 5; scripts/disco serial repl 10) timeout: 25s
⎿ === REPL Test (10s timeout) ===
Device: /dev/tty.usbmodem335D375F33382
Sending wake-up sequence...
… +9 lines (ctrl+o to expand)
⏺ REPL works, no stacktrace on boot. Let me test the import:
⏺ Bash(scripts/disco repl exec "import hardwaretest") timeout: 15s
⎿ Traceback (most recent call last):
File "", line 1, in
File "hardwaretest.py", line 1, in
… +6 lines (ctrl+o to expand)
⏺ Another LVGL issue in keyboard.py - lv.btnm doesn't exist in LVGL 8.x (now lv.buttonmatrix). Let me check:
⏺ Read(src/gui/components/keyboard.py)
⎿ Read 43 lines
⏺ Bash(scripts/disco repl exec "import lvgl as lv; print([x for x in dir(lv) if 'btn' timeout:
in x.lower()])") 15s
⎿ []
⏺ Bash(scripts/disco repl exec "import lvgl as lv; print([x for x in dir(lv) if timeout:
'button' in x.lower()])") 15s
⎿ ['BUTTONMATRIX_BUTTON_NONE', 'button', 'button_class', 'buttonmatrix', 'buttonmatrix_clas
s', 'imagebutton', 'imagebutton_class', 'list_button_class', 'msgbox_footer_button_class'
, 'msgbox_header_button_class']
⏺ Update(src/gui/components/keyboard.py)
⎿ Added 5 lines, removed 5 lines
4 from .theme import styles
5
6
7 -class HintKeyboard(lv.btnm):
7 +class HintKeyboard(lv.buttonmatrix):
8 def init(self, scr, *args, **kwargs):
9 super().init(scr, *args, **kwargs)
10 - self.hint = lv.btn(scr)
10 + self.hint = lv.button(scr)
11 self.hint.set_size(50, 60)
12 self.hint_lbl = lv.label(self.hint)
13 self.hint_lbl.set_text(" ")
14 - self.hint_lbl.set_style(0, styles["title"])
14 + self.hint_lbl.add_style(styles["title"], 0)
15 self.hint_lbl.set_size(50, 60)
16 - self.hint.set_hidden(True)
16 + self.hint.add_flag(lv.obj.FLAG.HIDDEN)
17 self.callback = None
18 - super().set_event_cb(self.cb)
18 + super().add_event_cb(self.cb, lv.EVENT.ALL, None)
19
20 def set_event_cb(self, callback):
21 self.callback = callback
⏺ Update(src/gui/components/keyboard.py)
⎿ Added 11 lines, removed 10 lines
23 def get_event_cb(self):
24 return self.callback
25
26 - def cb(self, obj, event):
27 - if event == lv.EVENT.PRESSING:
26 + def cb(self, event):
27 + code = event.get_code()
28 + obj = event.get_target()
29 + if code == lv.EVENT.PRESSING:
30 feed_touch()
29 - c = obj.get_active_btn_text()
31 + c = obj.get_selected_button_text()
32 if c is not None and len(c) <= 2:
31 - self.hint.set_hidden(False)
33 + self.hint.clear_flag(lv.obj.FLAG.HIDDEN)
34 self.hint_lbl.set_text(c)
33 - point = lv.point_t()
34 - indev = lv.indev_get_act()
35 - lv.indev_get_point(indev, point)
35 + indev = lv.indev_active()
36 + point = indev.get_point()
37 self.hint.set_pos(point.x - 25, point.y - 130)
38
38 - elif event == lv.EVENT.RELEASED:
39 - self.hint.set_hidden(True)
39 + elif code == lv.EVENT.RELEASED:
40 + self.hint.add_flag(lv.obj.FLAG.HIDDEN)
41
42 if self.callback is not None:
42 - self.callback(obj, event)
43 + self.callback(obj, code)
⏺ Bash(make debug 2>&1 | tail -10)
⎿ MPY hardwaretest.py
GEN build-STM32F469DISC/frozen_content.c
CC build-STM32F469DISC/frozen_content.c
… +7 lines (ctrl+o to expand)
⎿ Interrupted · What should Claude do instead?
⏺ Yes! The process is:
scripts/disco flash program bin/debug.bin --addr 0x08000000
scripts/disco ocd disconnect
sleep 5
scripts/disco serial repl 5
scripts/disco repl exec "import hardwaretest"
This shows the full stacktrace because the debug build has REPL enabled by default, so when hardwaretest.py fails to import (due to LVGL errors), the REPL stays alive and shows the error instead of crashing silently.
The import chain is:
hardwaretest.py → gui/init.py → gui/core.py → gui/common.py → gui/components/init.py → (qrcode.py, keyboard.py, etc.)
So import hardwaretest exercises the entire GUI component stack.
⏺ Yes, start at step 2. Suggestions:
Option A: disco repl test
Option B: disco repl import
Option C: disco boot test
I'd lean toward Option B - it's flexible and fits naturally with existing disco repl exec. Could default to hardwaretest if no arg given.
Want me to implement it?
⏺ Good point. Better options:
I'd go with disco repl import - it's explicit and fits with existing disco repl exec, disco repl modules, etc.
Usage:
disco repl import hardwaretest # import and show output/errors
disco repl import gui.components # test specific module
disco repl import # default to hardwaretest
Want me to implement this?
⏺ Read(scripts/disco_lib/commands/repl.py)
⎿ Read 329 lines
⏺ Update(scripts/disco_lib/commands/repl.py)
⎿ Added 54 lines
303 raise click.ClickException("Use : prefix for board paths (e.g., :/flash/
file.py)")
304
305
306 +@repl.command("import")
307 +@click.argument("module", default="hardwaretest")
308 +@click.option("--timeout", "-t", default=10, type=int, help="Response timeout in
+ seconds")
309 +def repl_import(module: str, timeout: int):
310 + """Import a module and show output/errors.
311 +
312 + Useful for testing boot sequence - imports the module that boot.py
313 + would normally run via pyb.main(). Shows any import errors or
314 + stacktraces that occur during module initialization.
315 +
316 + \b
317 + This is the recommended way to debug boot failures:
318 + 1. Flash debug firmware
319 + 2. Run: disco repl import
320 + 3. See any stacktraces from failed imports
321 +
322 + \b
323 + Examples:
324 + disco repl import # import hardwaretest (default)
325 + disco repl import gui # test GUI module only
326 + disco repl import gui.components # test specific submodule
327 + """
328 + from .ocd import _ocd_stop, _is_ocd_running
329 +
330 + # Stop OpenOCD if running (it blocks USB CDC)
331 + if _is_ocd_running():
332 + click.echo("Stopping OpenOCD...")
333 + _ocd_stop()
334 + time.sleep(1)
335 +
336 + # Wait for USB CDC device
337 + click.echo("Waiting for USB CDC device...")
338 + for i in range(10):
339 + dev = _ser.find_device()
340 + if dev:
341 + break
342 + time.sleep(1)
343 + else:
344 + raise click.ClickException("USB CDC device not found. Is miniUSB connect
+ed?")
345 +
346 + click.echo(f"Device: {dev}")
347 + click.echo(f"Importing '{module}'...")
348 + click.echo()
349 +
350 + try:
351 + output = _repl_exec(dev, f"import {module}", timeout=timeout)
352 + if output:
353 + click.echo(output)
354 + else:
355 + click.secho(f"Module '{module}' imported successfully (no output)",
+fg="green")
356 + except pyserial.SerialException as e:
357 + raise click.ClickException(f"Serial error: {e}")
358 +
359 +
360 @repl.command("rm")
361 @click.argument("path")
362 @click.option("--force", "-f", is_flag=True, help="Don't ask for confirmation")
⏺ Search(pattern: "def _ocd_stop|def _is_ocd_running", path: "scripts/disco_lib/commands",
output_mode: "content")
⎿ Found 0 lines
⏺ Search(pattern: "^def ", path: "scripts/disco_lib/commands/ocd.py", output_mode: "content")
⎿ Found 5 lines (ctrl+o to expand)
⏺ Read(scripts/disco_lib/commands/ocd.py)
⎿ Read 84 lines
⏺ Update(scripts/disco_lib/commands/repl.py)
⎿ Added 5 lines, removed 3 lines
325 disco repl import gui # test GUI module only
326 disco repl import gui.components # test specific submodule
327 """
328 - from .ocd import _ocd_stop, _is_ocd_running
328 + from ..openocd import OpenOCD
329
330 + ocd = OpenOCD()
331 +
332 # Stop OpenOCD if running (it blocks USB CDC)
331 - if _is_ocd_running():
333 + if ocd.is_running():
334 click.echo("Stopping OpenOCD...")
333 - _ocd_stop()
335 + ocd.stop()
336 time.sleep(1)
337
338 # Wait for USB CDC device
⏺ Now let me test it:
⏺ Bash(scripts/disco repl import --help)
⎿ Usage: disco repl import [OPTIONS] [MODULE]
⏺ Bash(scripts/disco repl import) timeout: 30s
⎿ Error: Exit code 1
Traceback (most recent call last):
File "/Users/kim/pubsrc/specter-diy.new/scripts/disco", line 20, in
cli()
~~~^^
File "/Users/kim/pubsrc/specter-diy.new/.venv/lib/python3.14/site-packages/click/core.py",
line 1485, in call
return self.main(*args, **kwargs)
~~~~~~~~~^^^^^^^^^^^^^^^^^
File "/Users/kim/pubsrc/specter-diy.new/.venv/lib/python3.14/site-packages/click/core.py",
line 1406, in main
rv = self.invoke(ctx)
… +17 lines (ctrl+o to see all)
⏺ Search(pattern: "def.*device", path: "scripts/disco_lib/serial.py", output_mode: "content")
⎿ Found 3 lines (ctrl+o to expand)
⏺ Read(scripts/disco_lib/serial.py)
⎿ Read 40 lines
⏺ Update(scripts/disco_lib/commands/repl.py)
⎿ Added 1 line, removed 1 line
338 # Wait for USB CDC device
339 click.echo("Waiting for USB CDC device...")
340 for i in range(10):
341 - dev = _ser.find_device()
341 + dev = _ser.auto_detect()
342 if dev:
343 break
344 time.sleep(1)
⏺ Bash(scripts/disco repl import) timeout: 30s
⎿ Waiting for USB CDC device...
Device: /dev/tty.usbmodem335D375F33382
Importing 'hardwaretest'...
… +10 lines (ctrl+o to expand)
⏺ Command works. Shows the keyboard.py error (we haven't flashed the fixed version yet). Let me flash and test:
⏺ Bash(scripts/disco ocd connect && scripts/disco flash program bin/debug.bin --addr timeout: 3m
0x08000000) 0s
⎿ Interrupted · What should Claude do instead?
⏺ Write(.history/debugging-python-boot-issues.md)
⎿ Wrote 125 lines to .history/debugging-python-boot-issues.md
1 # Debugging Python Boot Issues
2 Date: 2024-12-19
3 ## Problem
4 When MicroPython firmware fails during boot (e.g., due to import errors in frozen m
odules), the board may appear dead with no visible error output. Debug builds have
REPL enabled, but if the main script crashes on import, you won't see the stacktrac
e unless you manually trigger the import.
5 ## Solution: Iterative Debug Workflow
… +115 lines (ctrl+o to expand)
⏺ Created .history/debugging-python-boot-issues.md.
Now continuing with the fix - need to flash and test:
⏺ Bash(scripts/disco ocd connect && scripts/disco flash program bin/debug.bin --addr timeout: 3m
0x08000000) 0s
⎿ OpenOCD already running
=== Programming Firmware ===
File: /Users/kim/pubsrc/specter-diy.new/bin/debug.bin
… +21 lines (ctrl+o to expand)
⏺ Bash(scripts/disco repl import) timeout: 30s
⎿ Stopping OpenOCD...
OpenOCD stopped
Waiting for USB CDC device...
… +4 lines (ctrl+o to expand)
⏺ It says success, but let me verify - it used the wrong device (ST-LINK VCP instead of USB OTG CDC). Let me check:
⏺ Bash(scripts/disco serial list)
⎿ === Serial Devices ===
USB Modem devices (tty.*):
/dev/tty.usbmodem335D375F33382
… +5 lines (ctrl+o to expand)
⏺ Bash(scripts/disco repl import) timeout: 30s
⎿ Waiting for USB CDC device...
Device: /dev/tty.usbmodem335D375F33382
Importing 'hardwaretest'...
… +5 lines (ctrl+o to expand)
⏺ Progress! keyboard.py error is fixed. New error:
ImportError: no module named 'hosts.QRHost'
Let me check hardwaretest.py:
⏺ Bash(scripts/disco repl exec "import gui") timeout: 15s
⎿ (No content)
⏺ GUI imports fine. Let me check hardwaretest.py:
⏺ Read(src/hardwaretest.py)
⎿ Error reading file
⏺ Search(pattern: "**/hardwaretest.py")
⎿ Found 1 file (ctrl+o to expand)
⏺ Read(boot/debug/hardwaretest.py)
⎿ Read 93 lines
⏺ The hosts.QRHost import is failing. This is a different issue (not LVGL-related). Let me check if hosts module exists:
⏺ Search(pattern: "/hosts//*.py")
⎿ Found 5 files (ctrl+o to expand)
⏺ Read(src/hosts/init.py)
⎿ Read 5 lines
⏺ Bash(scripts/disco repl exec "import hosts.qr") timeout: 15s
⎿ Traceback (most recent call last):
File "", line 1, in
ImportError: no module named 'hosts.qr'
⏺ Bash(scripts/disco repl exec "import hosts") timeout: 15s
⎿ (No content)
⏺ Bash(scripts/disco repl exec "import hosts; print(dir(hosts))") timeout: 15s
⎿ ['class', 'name', 'dict', 'path']
⏺ Bash(scripts/disco repl exec "from hosts.core import Host") timeout: 15s
⎿ Traceback (most recent call last):
File "", line 1, in
ImportError: no module named 'hosts.core'
⏺ Bash(scripts/disco repl exec "import sys; print([m for m in sys.modules.keys() if timeout:
'host' in m])") 15s
⎿ ['hosts']
⏺ The LVGL fixes work (qrcode.py and keyboard.py errors are resolved).
The new error ImportError: no module named 'hosts.qr' is a different issue - looks like the hosts submodules aren't being frozen into the firmware properly. This is a build/freeze configuration issue, not LVGL-related.
Summary of what we fixed:
New issue discovered:
Want me to investigate the hosts module freeze issue, or commit the LVGL fixes first?