Skip to content

Instantly share code, notes, and snippets.

@Josverl
Last active June 30, 2025 23:31
Show Gist options
  • Save Josverl/72d11a63ab7611296addcc171528e86f to your computer and use it in GitHub Desktop.
Save Josverl/72d11a63ab7611296addcc171528e86f to your computer and use it in GitHub Desktop.
Notes for micropython debugpy
  • frame.f_code.co_filename is a qualified filename with no leading / for frozen modules.

  • attempring to access frame_f_locals in a frozen module causes a crash on ESP32, possibly caused by the fact that mpy files are not compiled with "debug symbols"/ access to local names.

import sys
from collections import OrderedDict as OD

try:
    import neopixel
    from machine import Pin
    np = neopixel.NeoPixel(Pin(15), 10)
except ImportError:
    print("neopixel module not found. Skipping NeoPixel initialization.")
    np = None

import asyncio

def tracer(frame, event, arg):
    try:
        try:
            filename = frame.f_code.co_filename
            co_name = frame.f_code.co_name
        except:
            filename = "<unknown>"
            co_name = "<unknown>"
        print(f"[{event:<10}] ({filename} {co_name} {frame.f_lineno} , {frame.f_lasti} ) ")
        # accessing locals this way crashes the ESP32 (dsee below)
        # f_locals = OD(sorted(frame.f_locals.items()))
        # print(f"{f_locals}")
        # del f_locals
    except Exception as e:
        pass
    return tracer

def f_normal(n):
    i = 0
    while i < n:
        print(i)
        i += 1



async def blink(num, period_ms):
    while True and np:
        np[num] = (255, 0, 0)
        np.write()
        await asyncio.sleep_ms(period_ms)
        np[num] = (0, 0, 0)
        np.write()
        await asyncio.sleep_ms(period_ms)

async def a_main():
    asyncio.create_task(blink(1, 700))
    asyncio.create_task(blink(2, 1700))
    await asyncio.sleep_ms(10_000)

def call_frozen(n):
    if np:
        np.fill((0, 0, 0))
        np.write()
    print("*" * 40)
    asyncio.run(a_main())


print("="*40)
print("Tracing normal function")
print("="*40)
sys.settrace(tracer)
# f_normal(1)
call_frozen(1)

sys.settrace(None)
print("="*40)
mpremote run src/check_settrace.py 
========================================
Tracing normal function
========================================
[call      ] (<stdin> call_frozen 51 , 0 ) 
[line      ] (<stdin> call_frozen 52 , 0 ) 
[call      ] (neopixel.py __len__ 1 , 0 ) 
[line      ] (neopixel.py __len__ 1 , 0 ) 
[return    ] (neopixel.py __len__ 1 , 1 ) 
[line      ] (<stdin> call_frozen 53 , 0 ) 
[call      ] (neopixel.py fill 1 , 0 ) 
[line      ] (neopixel.py fill 1 , 0 ) 
[return    ] (neopixel.py fill 1 , 27 ) 
[line      ] (<stdin> call_frozen 54 , 10 ) 
[call      ] (neopixel.py write 1 , 0 ) 
[line      ] (neopixel.py write 1 , 0 ) 
[return    ] (neopixel.py write 1 , 12 ) 
[line      ] (<stdin> call_frozen 55 , 17 ) 
****************************************
[line      ] (<stdin> call_frozen 56 , 26 ) 
[call      ] (asyncio/core.py run 1 , 0 ) 
[line      ] (asyncio/core.py run 1 , 0 ) 
[call      ] (asyncio/core.py create_task 1 , 0 ) 
[line      ] (asyncio/core.py create_task 1 , 0 ) 
[return    ] (asyncio/core.py create_task 1 , 31 ) 
[call      ] (asyncio/core.py run_until_complete 1 , 0 ) 
[line      ] (asyncio/core.py run_until_complete 1 , 0 ) 
[call      ] (asyncio/core.py wait_io_event 1 , 0 ) 
[line      ] (asyncio/core.py wait_io_event 1 , 0 ) 
[return    ] (asyncio/core.py wait_io_event 1 , 9 ) 
[call      ] (<stdin> a_main 46 , 0 ) 
[line      ] (<stdin> a_main 47 , 0 ) 
[call      ] (asyncio/core.py create_task 1 , 0 ) 
[line      ] (asyncio/core.py create_task 1 , 0 ) 
[return    ] (asyncio/core.py create_task 1 , 31 ) 
[line      ] (<stdin> a_main 48 , 12 ) 
[call      ] (asyncio/core.py create_task 1 , 0 ) 
[line      ] (asyncio/core.py create_task 1 , 0 ) 
[return    ] (asyncio/core.py create_task 1 , 31 ) 
[line      ] (<stdin> a_main 49 , 27 ) 
[call      ] (asyncio/core.py sleep_ms 1 , 0 ) 
[line      ] (asyncio/core.py sleep_ms 1 , 0 ) 
[return    ] (asyncio/core.py sleep_ms 1 , 15 ) 
[call      ] (asyncio/core.py __iter__ 1 , 0 ) 
[line      ] (asyncio/core.py __iter__ 1 , 0 ) 
[return    ] (asyncio/core.py __iter__ 1 , 0 ) 
[call      ] (asyncio/core.py __next__ 1 , 0 ) 
[line      ] (asyncio/core.py __next__ 1 , 0 ) 
[return    ] (asyncio/core.py __next__ 1 , 22 ) 
[call      ] (asyncio/core.py wait_io_event 1 , 0 ) 
[line      ] (asyncio/core.py wait_io_event 1 , 0 ) 
[return    ] (asyncio/core.py wait_io_event 1 , 9 ) 
[call      ] (<stdin> blink 37 , 0 ) 
[line      ] (<stdin> blink 38 , 0 ) 
[line      ] (<stdin> blink 44 , 0 ) 
[call      ] (neopixel.py __len__ 1 , 0 ) 
[line      ] (neopixel.py __len__ 1 , 0 ) 
[return    ] (neopixel.py __len__ 1 , 1 ) 
[line      ] (<stdin> blink 39 , 0 ) 
[call      ] (neopixel.py __setitem__ 1 , 0 ) 
[line      ] (neopixel.py __setitem__ 1 , 0 ) 
[return    ] (neopixel.py __setitem__ 1 , 22 ) 
[line      ] (<stdin> blink 40 , 0 ) 
[call      ] (neopixel.py write 1 , 0 ) 
[line      ] (neopixel.py write 1 , 0 ) 
[return    ] (neopixel.py write 1 , 12 ) 
[line      ] (<stdin> blink 41 , 12 ) 
[call      ] (asyncio/core.py sleep_ms 1 , 0 ) 
[line      ] (asyncio/core.py sleep_ms 1 , 0 ) 
[return    ] (asyncio/core.py sleep_ms 1 , 15 ) 
[call      ] (asyncio/core.py __iter__ 1 , 0 ) 
[line      ] (asyncio/core.py __iter__ 1 , 0 ) 
[return    ] (asyncio/core.py __iter__ 1 , 0 ) 
[call      ] (asyncio/core.py __next__ 1 , 0 ) 
[line      ] (asyncio/core.py __next__ 1 , 0 ) 
[return    ] (asyncio/core.py __next__ 1 , 22 ) 
[call      ] (asyncio/core.py wait_io_event 1 , 0 ) 
[line      ] (asyncio/core.py wait_io_event 1 , 0 ) 
[return    ] (asyncio/core.py wait_io_event 1 , 9 ) 
[call      ] (<stdin> blink 37 , 0 ) 
[line      ] (<stdin> blink 38 , 0 ) 
[line      ] (<stdin> blink 44 , 0 ) 
[call      ] (neopixel.py __len__ 1 , 0 ) 
[line      ] (neopixel.py __len__ 1 , 0 ) 
[return    ] (neopixel.py __len__ 1 , 1 ) 
[line      ] (<stdin> blink 39 , 0 ) 
[call      ] (neopixel.py __setitem__ 1 , 0 ) 
[line      ] (neopixel.py __setitem__ 1 , 0 ) 
[return    ] (neopixel.py __setitem__ 1 , 22 ) 
[line      ] (<stdin> blink 40 , 0 ) 
[call      ] (neopixel.py write 1 , 0 ) 
[line      ] (neopixel.py write 1 , 0 ) 
[return    ] (neopixel.py write 1 , 12 ) 
[line      ] (<stdin> blink 41 , 12 ) 
[call      ] (asyncio/core.py sleep_ms 1 , 0 ) 
[line      ] (asyncio/core.py sleep_ms 1 , 0 ) 
[return    ] (asyncio/core.py sleep_ms 1 , 15 ) 
[call      ] (asyncio/core.py __iter__ 1 , 0 ) 
[line      ] (asyncio/core.py __iter__ 1 , 0 ) 
[return    ] (asyncio/core.py __iter__ 1 , 0 ) 
[call      ] (asyncio/core.py __next__ 1 , 0 ) 
[line      ] (asyncio/core.py __next__ 1 , 0 ) 
[return    ] (asyncio/core.py __next__ 1 , 22 ) 
[call      ] (asyncio/core.py wait_io_event 1 , 0 ) 
[line      ] (asyncio/core.py wait_io_event 1 , 0 ) 
[return    ] (asyncio/core.py wait_io_event 1 , 9 ) 
[call      ] (asyncio/core.py wait_io_event 1 , 0 ) 
[line      ] (asyncio/core.py wait_io_event 1 , 0 ) 
[return    ] (asyncio/core.py wait_io_event 1 , 9 ) 
[call      ] (<stdin> blink 41 , 24 ) 
[line      ] (<stdin> blink 41 , 24 ) 
[call      ] (asyncio/core.py __next__ 1 , 0 ) 
[line      ] (asyncio/core.py __next__ 1 , 0 ) 
[exception ] (asyncio/core.py __next__ 1 , 33 ) 
[line      ] (<stdin> blink 42 , 24 ) 
[call      ] (neopixel.py __setitem__ 1 , 0 ) 
[line      ] (neopixel.py __setitem__ 1 , 0 ) 
[return    ] (neopixel.py __setitem__ 1 , 22 ) 
[line      ] (<stdin> blink 43 , 24 ) 
[call      ] (neopixel.py write 1 , 0 ) 
[line      ] (neopixel.py write 1 , 0 ) 
[return    ] (neopixel.py write 1 , 12 ) 
[line      ] (<stdin> blink 44 , 36 ) 
[call      ] (asyncio/core.py sleep_ms 1 , 0 ) 
[line      ] (asyncio/core.py sleep_ms 1 , 0 ) 
[return    ] (asyncio/core.py sleep_ms 1 , 15 ) 
[call      ] (asyncio/core.py __iter__ 1 , 0 ) 
[line      ] (asyncio/core.py __iter__ 1 , 0 ) 
[return    ] (asyncio/core.py __iter__ 1 , 0 ) 
[call      ] (asyncio/core.py __next__ 1 , 0 ) 
[line      ] (asyncio/core.py __next__ 1 , 0 ) 
[return    ] (asyncio/core.py __next__ 1 , 22 ) 
[call      ] (asyncio/core.py wait_io_event 1 , 0 ) 
[line      ] (asyncio/core.py wait_io_event 1 , 0 ) 
[return    ] (asyncio/core.py wait_io_event 1 , 9 ) 
[call      ] (asyncio/core.py wait_io_event 1 , 0 ) 
[line      ] (asyncio/core.py wait_io_event 1 , 0 ) 
[return    ] (asyncio/core.py wait_io_event 1 , 9 ) 
[call      ] (<stdin> blink 44 , 48 ) 
[line      ] (<stdin> blink 44 , 48 ) 
[call      ] (asyncio/core.py __next__ 1 , 0 ) 
[line      ] (asyncio/core.py __next__ 1 , 0 ) 
[exception ] (asyncio/core.py __next__ 1 , 33 ) 
[call      ] (neopixel.py __len__ 1 , 0 ) 
[line      ] (neopixel.py __len__ 1 , 0 ) 
[return    ] (neopixel.py __len__ 1 , 1 ) 
[line      ] (<stdin> blink 39 , 48 ) 
[call      ] (neopixel.py __setitem__ 1 , 0 ) 
[line      ] (neopixel.py __setitem__ 1 , 0 ) 
[return    ] (neopixel.py __setitem__ 1 , 22 ) 
[line      ] (<stdin> blink 40 , 48 ) 
[call      ] (neopixel.py write 1 , 0 ) 
[line      ] (neopixel.py write 1 , 0 ) 
[return    ] (neopixel.py write 1 , 12 ) 
[line      ] (<stdin> blink 41 , 12 ) 
[call      ] (asyncio/core.py sleep_ms 1 , 0 ) 
[line      ] (asyncio/core.py sleep_ms 1 , 0 ) 
[return    ] (asyncio/core.py sleep_ms 1 , 15 ) 
[call      ] (asyncio/core.py __iter__ 1 , 0 ) 
[line      ] (asyncio/core.py __iter__ 1 , 0 ) 
[return    ] (asyncio/core.py __iter__ 1 , 0 ) 
[call      ] (asyncio/core.py __next__ 1 , 0 ) 
[line      ] (asyncio/core.py __next__ 1 , 0 ) 
[return    ] (asyncio/core.py __next__ 1 , 22 ) 
[call      ] (asyncio/core.py wait_io_event 1 , 0 ) 
[line      ] (asyncio/core.py wait_io_event 1 , 0 ) 
[return    ] (asyncio/core.py wait_io_event 1 , 9 ) 
[call      ] (asyncio/core.py wait_io_event 1 , 0 ) 
[line      ] (asyncio/core.py wait_io_event 1 , 0 ) 
[return    ] (asyncio/core.py wait_io_event 1 , 9 ) 
[call      ] (<stdin> blink 41 , 24 ) 
[line      ] (<stdin> blink 41 , 24 ) 
[call      ] (asyncio/core.py __next__ 1 , 0 ) 
[line      ] (asyncio/core.py __next__ 1 , 0 ) 
[exception ] (asyncio/core.py __next__ 1 , 33 ) 
[line      ] (<stdin> blink 42 , 24 ) 
[call      ] (neopixel.py __setitem__ 1 , 0 ) 
[line      ] (neopixel.py __setitem__ 1 , 0 ) 
[return    ] (neopixel.py __setitem__ 1 , 22 ) 
[line      ] (<stdin> blink 43 , 24 ) 
[call      ] (neopixel.py write 1 , 0 ) 
[line      ] (neopixel.py write 1 , 0 ) 
[return    ] (neopixel.py write 1 , 12 ) 
[line      ] (<stdin> blink 44 , 36 ) 
[call      ] (asyncio/core.py sleep_ms 1 , 0 ) 
[line      ] (asyncio/core.py sleep_ms 1 , 0 ) 
[return    ] (asyncio/core.py sleep_ms 1 , 15 ) 
[call      ] (asyncio/core.py __iter__ 1 , 0 ) 
[line      ] (asyncio/core.py __iter__ 1 , 0 ) 
[return    ] (asyncio/core.py __iter__ 1 , 0 ) 
[call      ] (asyncio/core.py __next__ 1 , 0 ) 
[line      ] (asyncio/core.py __next__ 1 , 0 ) 
[return    ] (asyncio/core.py __next__ 1 , 22 ) 
[call      ] (asyncio/core.py wait_io_event 1 , 0 ) 
[line      ] (asyncio/core.py wait_io_event 1 , 0 ) 
[return    ] (asyncio/core.py wait_io_event 1 , 9 ) 
[call      ] (asyncio/core.py wait_io_event 1 , 0 ) 
[line      ] (asyncio/core.py wait_io_event 1 , 0 ) 
[return    ] (asyncio/core.py wait_io_event 1 , 9 ) 
[call      ] (<stdin> blink 41 , 24 ) 
[line      ] (<stdin> blink 41 , 24 ) 
[call      ] (asyncio/core.py __next__ 1 , 0 ) 
[line      ] (asyncio/core.py __next__ 1 , 0 ) 
[exception ] (asyncio/core.py __next__ 1 , 33 ) 
[line      ] (<stdin> blink 42 , 24 ) 
[call      ] (neopixel.py __setitem__ 1 , 0 ) 
[line      ] (neopixel.py __setitem__ 1 , 0 ) 
[return    ] (neopixel.py __setitem__ 1 , 22 ) 
[line      ] (<stdin> blink 43 , 24 ) 
[call      ] (neopixel.py write 1 , 0 ) 
[line      ] (neopixel.py write 1 , 0 ) 
[return    ] (neopixel.py write 1 , 12 ) 
[line      ] (<stdin> blink 44 , 36 ) 
[call      ] (asyncio/core.py sleep_ms 1 , 0 ) 
[line      ] (asyncio/core.py sleep_ms 1 , 0 ) 
[return    ] (asyncio/core.py sleep_ms 1 , 15 ) 
[call      ] (asyncio/core.py __iter__ 1 , 0 ) 
[line      ] (asyncio/core.py __iter__ 1 , 0 ) 
[return    ] (asyncio/core.py __iter__ 1 , 0 ) 
[call      ] (asyncio/core.py __next__ 1 , 0 ) 
[line      ] (asyncio/core.py __next__ 1 , 0 ) 
[return    ] (asyncio/core.py __next__ 1 , 22 ) 
[call      ] (asyncio/core.py wait_io_event 1 , 0 ) 
[line      ] (asyncio/core.py wait_io_event 1 , 0 ) 
[return    ] (asyncio/core.py wait_io_event 1 , 9 ) 
[call      ] (asyncio/core.py wait_io_event 1 , 0 ) 
[line      ] (asyncio/core.py wait_io_event 1 , 0 ) 
[return    ] (asyncio/core.py wait_io_event 1 , 9 ) 
[call      ] (<stdin> blink 44 , 48 ) 
[line      ] (<stdin> blink 44 , 48 ) 
[call      ] (asyncio/core.py __next__ 1 , 0 ) 
[line      ] (asyncio/core.py __next__ 1 , 0 ) 
[exception ] (asyncio/core.py __next__ 1 , 33 ) 
[call      ] (neopixel.py __len__ 1 , 0 ) 
[line      ] (neopixel.py __len__ 1 , 0 ) 
[return    ] (neopixel.py __len__ 1 , 1 ) 
[line      ] (<stdin> blink 39 , 48 ) 
[call      ] (neopixel.py __setitem__ 1 , 0 ) 
[line      ] (neopixel.py __setitem__ 1 , 0 ) 
[return    ] (neopixel.py __setitem__ 1 , 22 ) 
[line      ] (<stdin> blink 40 , 48 ) 
[call      ] (neopixel.py write 1 , 0 ) 
[line      ] (neopixel.py write 1 , 0 ) 
[return    ] (neopixel.py write 1 , 12 ) 
[line      ] (<stdin> blink 41 , 12 ) 
[call      ] (asyncio/core.py sleep_ms 1 , 0 ) 
[line      ] (asyncio/core.py sleep_ms 1 , 0 ) 
[return    ] (asyncio/core.py sleep_ms 1 , 15 ) 
[call      ] (asyncio/core.py __iter__ 1 , 0 ) 
[line      ] (asyncio/core.py __iter__ 1 , 0 ) 
[return    ] (asyncio/core.py __iter__ 1 , 0 ) 
[call      ] (asyncio/core.py __next__ 1 , 0 ) 
[line      ] (asyncio/core.py __next__ 1 , 0 ) 
[return    ] (asyncio/core.py __next__ 1 , 22 ) 
[call      ] (asyncio/core.py wait_io_event 1 , 0 ) 
[line      ] (asyncio/core.py wait_io_event 1 , 0 ) 
[return    ] (asyncio/core.py wait_io_event 1 , 9 ) 
[call      ] (asyncio/core.py wait_io_event 1 , 0 ) 
[line      ] (asyncio/core.py wait_io_event 1 , 0 ) 
[return    ] (asyncio/core.py wait_io_event 1 , 9 ) 
[call      ] (<stdin> blink 44 , 48 ) 
[line      ] (<stdin> blink 44 , 48 ) 
[call      ] (asyncio/core.py __next__ 1 , 0 ) 
[line      ] (asyncio/core.py __next__ 1 , 0 ) 
[exception ] (asyncio/core.py __next__ 1 , 33 ) 
[call      ] (neopixel.py __len__ 1 , 0 ) 
[line      ] (neopixel.py __len__ 1 , 0 ) 
[return    ] (neopixel.py __len__ 1 , 1 ) 
[line      ] (<stdin> blink 39 , 48 ) 
[call      ] (neopixel.py __setitem__ 1 , 0 ) 
[line      ] (neopixel.py __setitem__ 1 , 0 ) 
[return    ] (neopixel.py __setitem__ 1 , 22 ) 
[line      ] (<stdin> blink 40 , 48 ) 
[call      ] (neopixel.py write 1 , 0 ) 
[line      ] (neopixel.py write 1 , 0 ) 
[return    ] (neopixel.py write 1 , 12 ) 
[line      ] (<stdin> blink 41 , 12 ) 
[call      ] (asyncio/core.py sleep_ms 1 , 0 ) 
[line      ] (asyncio/core.py sleep_ms 1 , 0 ) 
[return    ] (asyncio/core.py sleep_ms 1 , 15 ) 
[call      ] (asyncio/core.py __iter__ 1 , 0 ) 
[line      ] (asyncio/core.py __iter__ 1 , 0 ) 
[return    ] (asyncio/core.py __iter__ 1 , 0 ) 
[call      ] (asyncio/core.py __next__ 1 , 0 ) 
[line      ] (asyncio/core.py __next__ 1 , 0 ) 
[return    ] (asyncio/core.py __next__ 1 , 22 ) 
[call      ] (asyncio/core.py wait_io_event 1 , 0 ) 
[line      ] (asyncio/core.py wait_io_event 1 , 0 ) 
[return    ] (asyncio/core.py wait_io_event 1 , 9 ) 
[call      ] (asyncio/core.py wait_io_event 1 , 0 ) 
[line      ] (asyncio/core.py wait_io_event 1 , 0 ) 
[return    ] (asyncio/core.py wait_io_event 1 , 9 ) 
[call      ] (<stdin> blink 41 , 24 ) 
[line      ] (<stdin> blink 41 , 24 ) 
[call      ] (asyncio/core.py __next__ 1 , 0 ) 
[line      ] (asyncio/core.py __next__ 1 , 0 ) 
[exception ] (asyncio/core.py __next__ 1 , 33 ) 
[line      ] (<stdin> blink 42 , 24 ) 
[call      ] (neopixel.py __setitem__ 1 , 0 ) 
[line      ] (neopixel.py __setitem__ 1 , 0 ) 
[return    ] (neopixel.py __setitem__ 1 , 22 ) 
[line      ] (<stdin> blink 43 , 24 ) 
[call      ] (neopixel.py write 1 , 0 ) 
[line      ] (neopixel.py write 1 , 0 ) 
[return    ] (neopixel.py write 1 , 12 ) 
[line      ] (<stdin> blink 44 , 36 ) 
[call      ] (asyncio/core.py sleep_ms 1 , 0 ) 
[line      ] (asyncio/core.py sleep_ms 1 , 0 ) 
[return    ] (asyncio/core.py sleep_ms 1 , 15 ) 
[call      ] (asyncio/core.py __iter__ 1 , 0 ) 
[line      ] (asyncio/core.py __iter__ 1 , 0 ) 
[return    ] (asyncio/core.py __iter__ 1 , 0 ) 
[call      ] (asyncio/core.py __next__ 1 , 0 ) 
[line      ] (asyncio/core.py __next__ 1 , 0 ) 
[return    ] (asyncio/core.py __next__ 1 , 22 ) 
[call      ] (asyncio/core.py wait_io_event 1 , 0 ) 
[line      ] (asyncio/core.py wait_io_event 1 , 0 ) 
[return    ] (asyncio/core.py wait_io_event 1 , 9 ) 
[call      ] (asyncio/core.py wait_io_event 1 , 0 ) 
[line      ] (asyncio/core.py wait_io_event 1 , 0 ) 
[return    ] (asyncio/core.py wait_io_event 1 , 9 ) 
[call      ] (<stdin> blink 44 , 48 ) 
[line      ] (<stdin> blink 44 , 48 ) 
[call      ] (asyncio/core.py __next__ 1 , 0 ) 
[line      ] (asyncio/core.py __next__ 1 , 0 ) 
[exception ] (asyncio/core.py __next__ 1 , 33 ) 
[call      ] (neopixel.py __len__ 1 , 0 ) 
[line      ] (neopixel.py __len__ 1 , 0 ) 
[return    ] (neopixel.py __len__ 1 , 1 ) 
[line      ] (<stdin> blink 39 , 48 ) 
[call      ] (neopixel.py __setitem__ 1 , 0 ) 
[line      ] (neopixel.py __setitem__ 1 , 0 ) 
[return    ] (neopixel.py __setitem__ 1 , 22 ) 
[line      ] (<stdin> blink 40 , 48 ) 
[call      ] (neopixel.py write 1 , 0 ) 
[line      ] (neopixel.py write 1 , 0 ) 
[return    ] (neopixel.py write 1 , 12 ) 
[line      ] (<stdin> blink 41 , 12 ) 
[call      ] (asyncio/core.py sleep_ms 1 , 0 ) 
[line      ] (asyncio/core.py sleep_ms 1 , 0 ) 
[return    ] (asyncio/core.py sleep_ms 1 , 15 ) 
[call      ] (asyncio/core.py __iter__ 1 , 0 ) 
[line      ] (asyncio/core.py __iter__ 1 , 0 ) 
[return    ] (asyncio/core.py __iter__ 1 , 0 ) 
[call      ] (asyncio/core.py __next__ 1 , 0 ) 
[line      ] (asyncio/core.py __next__ 1 , 0 ) 
[return    ] (asyncio/core.py __next__ 1 , 22 ) 
[call      ] (asyncio/core.py wait_io_event 1 , 0 ) 
[line      ] (asyncio/core.py wait_io_event 1 , 0 ) 
[return    ] (asyncio/core.py wait_io_event 1 , 9 ) 
[call      ] (asyncio/core.py wait_io_event 1 , 0 ) 
[line      ] (asyncio/core.py wait_io_event 1 , 0 ) 
[return    ] (asyncio/core.py wait_io_event 1 , 9 ) 
[call      ] (<stdin> blink 41 , 24 ) 
[line      ] (<stdin> blink 41 , 24 ) 
[call      ] (asyncio/core.py __next__ 1 , 0 ) 
[line      ] (asyncio/core.py __next__ 1 , 0 ) 
[exception ] (asyncio/core.py __next__ 1 , 33 ) 
[line      ] (<stdin> blink 42 , 24 ) 
[call      ] (neopixel.py __setitem__ 1 , 0 ) 
[line      ] (neopixel.py __setitem__ 1 , 0 ) 
[return    ] (neopixel.py __setitem__ 1 , 22 ) 
[line      ] (<stdin> blink 43 , 24 ) 
[call      ] (neopixel.py write 1 , 0 ) 
[line      ] (neopixel.py write 1 , 0 ) 
[return    ] (neopixel.py write 1 , 12 ) 
[line      ] (<stdin> blink 44 , 36 ) 
[call      ] (asyncio/core.py sleep_ms 1 , 0 ) 
[line      ] (asyncio/core.py sleep_ms 1 , 0 ) 
[return    ] (asyncio/core.py sleep_ms 1 , 15 ) 
[call      ] (asyncio/core.py __iter__ 1 , 0 ) 
[line      ] (asyncio/core.py __iter__ 1 , 0 ) 
[return    ] (asyncio/core.py __iter__ 1 , 0 ) 
[call      ] (asyncio/core.py __next__ 1 , 0 ) 
[line      ] (asyncio/core.py __next__ 1 , 0 ) 
[return    ] (asyncio/core.py __next__ 1 , 22 ) 
[call      ] (asyncio/core.py wait_io_event 1 , 0 ) 
[line      ] (asyncio/core.py wait_io_event 1 , 0 ) 
[return    ] (asyncio/core.py wait_io_event 1 , 9 ) 
[call      ] (<stdin> blink 41 , 24 ) 
[line      ] (<stdin> blink 41 , 24 ) 
[call      ] (asyncio/core.py __next__ 1 , 0 ) 
[line      ] (asyncio/core.py __next__ 1 , 0 ) 
[exception ] (asyncio/core.py __next__ 1 , 33 ) 
[line      ] (<stdin> blink 42 , 24 ) 
[call      ] (neopixel.py __setitem__ 1 , 0 ) 
[line      ] (neopixel.py __setitem__ 1 , 0 ) 
[return    ] (neopixel.py __setitem__ 1 , 22 ) 
[line      ] (<stdin> blink 43 , 24 ) 
[call      ] (neopixel.py write 1 , 0 ) 
[line      ] (neopixel.py write 1 , 0 ) 
[return    ] (neopixel.py write 1 , 12 ) 
[line      ] (<stdin> blink 44 , 36 ) 
[call      ] (asyncio/core.py sleep_ms 1 , 0 ) 
[line      ] (asyncio/core.py sleep_ms 1 , 0 ) 
[return    ] (asyncio/core.py sleep_ms 1 , 15 ) 
[call      ] (asyncio/core.py __iter__ 1 , 0 ) 
[line      ] (asyncio/core.py __iter__ 1 , 0 ) 
[return    ] (asyncio/core.py __iter__ 1 , 0 ) 
[call      ] (asyncio/core.py __next__ 1 , 0 ) 
[line      ] (asyncio/core.py __next__ 1 , 0 ) 
[return    ] (asyncio/core.py __next__ 1 , 22 ) 
[call      ] (asyncio/core.py wait_io_event 1 , 0 ) 
[line      ] (asyncio/core.py wait_io_event 1 , 0 ) 
[return    ] (asyncio/core.py wait_io_event 1 , 9 ) 
[call      ] (asyncio/core.py wait_io_event 1 , 0 ) 
[line      ] (asyncio/core.py wait_io_event 1 , 0 ) 
[return    ] (asyncio/core.py wait_io_event 1 , 9 ) 
[call      ] (<stdin> blink 44 , 48 ) 
[line      ] (<stdin> blink 44 , 48 ) 
[call      ] (asyncio/core.py __next__ 1 , 0 ) 
[line      ] (asyncio/core.py __next__ 1 , 0 ) 
[exception ] (asyncio/core.py __next__ 1 , 33 ) 
[call      ] (neopixel.py __len__ 1 , 0 ) 
[line      ] (neopixel.py __len__ 1 , 0 ) 
[return    ] (neopixel.py __len__ 1 , 1 ) 
[line      ] (<stdin> blink 39 , 48 ) 
[call      ] (neopixel.py __setitem__ 1 , 0 ) 
[line      ] (neopixel.py __setitem__ 1 , 0 ) 
[return    ] (neopixel.py __setitem__ 1 , 22 ) 
[line      ] (<stdin> blink 40 , 48 ) 
[call      ] (neopixel.py write 1 , 0 ) 
[line      ] (neopixel.py write 1 , 0 ) 
[return    ] (neopixel.py write 1 , 12 ) 
[line      ] (<stdin> blink 41 , 12 ) 
[call      ] (asyncio/core.py sleep_ms 1 , 0 ) 
[line      ] (asyncio/core.py sleep_ms 1 , 0 ) 
[return    ] (asyncio/core.py sleep_ms 1 , 15 ) 
[call      ] (asyncio/core.py __iter__ 1 , 0 ) 
[line      ] (asyncio/core.py __iter__ 1 , 0 ) 
[return    ] (asyncio/core.py __iter__ 1 , 0 ) 
[call      ] (asyncio/core.py __next__ 1 , 0 ) 
[line      ] (asyncio/core.py __next__ 1 , 0 ) 
[return    ] (asyncio/core.py __next__ 1 , 22 ) 
[call      ] (asyncio/core.py wait_io_event 1 , 0 ) 
[line      ] (asyncio/core.py wait_io_event 1 , 0 ) 
[return    ] (asyncio/core.py wait_io_event 1 , 9 ) 
[call      ] (asyncio/core.py wait_io_event 1 , 0 ) 
[line      ] (asyncio/core.py wait_io_event 1 , 0 ) 
[return    ] (asyncio/core.py wait_io_event 1 , 9 ) 
[call      ] (<stdin> blink 41 , 24 ) 
[line      ] (<stdin> blink 41 , 24 ) 
[call      ] (asyncio/core.py __next__ 1 , 0 ) 
[line      ] (asyncio/core.py __next__ 1 , 0 ) 
[exception ] (asyncio/core.py __next__ 1 , 33 ) 
[line      ] (<stdin> blink 42 , 24 ) 
[call      ] (neopixel.py __setitem__ 1 , 0 ) 
[line      ] (neopixel.py __setitem__ 1 , 0 ) 
[return    ] (neopixel.py __setitem__ 1 , 22 ) 
[line      ] (<stdin> blink 43 , 24 ) 
[call      ] (neopixel.py write 1 , 0 ) 
[line      ] (neopixel.py write 1 , 0 ) 
[return    ] (neopixel.py write 1 , 12 ) 
[line      ] (<stdin> blink 44 , 36 ) 
[call      ] (asyncio/core.py sleep_ms 1 , 0 ) 
[line      ] (asyncio/core.py sleep_ms 1 , 0 ) 
[return    ] (asyncio/core.py sleep_ms 1 , 15 ) 
[call      ] (asyncio/core.py __iter__ 1 , 0 ) 
[line      ] (asyncio/core.py __iter__ 1 , 0 ) 
[return    ] (asyncio/core.py __iter__ 1 , 0 ) 
[call      ] (asyncio/core.py __next__ 1 , 0 ) 
[line      ] (asyncio/core.py __next__ 1 , 0 ) 
[return    ] (asyncio/core.py __next__ 1 , 22 ) 
[call      ] (asyncio/core.py wait_io_event 1 , 0 ) 
[line      ] (asyncio/core.py wait_io_event 1 , 0 ) 
[return    ] (asyncio/core.py wait_io_event 1 , 9 ) 
[call      ] (<stdin> blink 44 , 48 ) 
[line      ] (<stdin> blink 44 , 48 ) 
[call      ] (asyncio/core.py __next__ 1 , 0 ) 
[line      ] (asyncio/core.py __next__ 1 , 0 ) 
[exception ] (asyncio/core.py __next__ 1 , 33 ) 
[call      ] (neopixel.py __len__ 1 , 0 ) 
[line      ] (neopixel.py __len__ 1 , 0 ) 
[return    ] (neopixel.py __len__ 1 , 1 ) 
[line      ] (<stdin> blink 39 , 48 ) 
[call      ] (neopixel.py __setitem__ 1 , 0 ) 
[line      ] (neopixel.py __setitem__ 1 , 0 ) 
[return    ] (neopixel.py __setitem__ 1 , 22 ) 
[line      ] (<stdin> blink 40 , 48 ) 
[call      ] (neopixel.py write 1 , 0 ) 
[line      ] (neopixel.py write 1 , 0 ) 
[return    ] (neopixel.py write 1 , 12 ) 
[line      ] (<stdin> blink 41 , 12 ) 
[call      ] (asyncio/core.py sleep_ms 1 , 0 ) 
[line      ] (asyncio/core.py sleep_ms 1 , 0 ) 
[return    ] (asyncio/core.py sleep_ms 1 , 15 ) 
[call      ] (asyncio/core.py __iter__ 1 , 0 ) 
[line      ] (asyncio/core.py __iter__ 1 , 0 ) 
[return    ] (asyncio/core.py __iter__ 1 , 0 ) 
[call      ] (asyncio/core.py __next__ 1 , 0 ) 
[line      ] (asyncio/core.py __next__ 1 , 0 ) 
[return    ] (asyncio/core.py __next__ 1 , 22 ) 
[call      ] (asyncio/core.py wait_io_event 1 , 0 ) 
[line      ] (asyncio/core.py wait_io_event 1 , 0 ) 
[return    ] (asyncio/core.py wait_io_event 1 , 9 ) 
[call      ] (asyncio/core.py wait_io_event 1 , 0 ) 
[line      ] (asyncio/core.py wait_io_event 1 , 0 ) 
[return    ] (asyncio/core.py wait_io_event 1 , 9 ) 
[call      ] (<stdin> blink 44 , 48 ) 
[line      ] (<stdin> blink 44 , 48 ) 
[call      ] (asyncio/core.py __next__ 1 , 0 ) 
[line      ] (asyncio/core.py __next__ 1 , 0 ) 
[exception ] (asyncio/core.py __next__ 1 , 33 ) 
[call      ] (neopixel.py __len__ 1 , 0 ) 
[line      ] (neopixel.py __len__ 1 , 0 ) 
[return    ] (neopixel.py __len__ 1 , 1 ) 
[line      ] (<stdin> blink 39 , 48 ) 
[call      ] (neopixel.py __setitem__ 1 , 0 ) 
[line      ] (neopixel.py __setitem__ 1 , 0 ) 
[return    ] (neopixel.py __setitem__ 1 , 22 ) 
[line      ] (<stdin> blink 40 , 48 ) 
[call      ] (neopixel.py write 1 , 0 ) 
[line      ] (neopixel.py write 1 , 0 ) 
[return    ] (neopixel.py write 1 , 12 ) 
[line      ] (<stdin> blink 41 , 12 ) 
[call      ] (asyncio/core.py sleep_ms 1 , 0 ) 
[line      ] (asyncio/core.py sleep_ms 1 , 0 ) 
[return    ] (asyncio/core.py sleep_ms 1 , 15 ) 
[call      ] (asyncio/core.py __iter__ 1 , 0 ) 
[line      ] (asyncio/core.py __iter__ 1 , 0 ) 
[return    ] (asyncio/core.py __iter__ 1 , 0 ) 
[call      ] (asyncio/core.py __next__ 1 , 0 ) 
[line      ] (asyncio/core.py __next__ 1 , 0 ) 
[return    ] (asyncio/core.py __next__ 1 , 22 ) 
[call      ] (asyncio/core.py wait_io_event 1 , 0 ) 
[line      ] (asyncio/core.py wait_io_event 1 , 0 ) 
[return    ] (asyncio/core.py wait_io_event 1 , 9 ) 
[call      ] (asyncio/core.py wait_io_event 1 , 0 ) 
[line      ] (asyncio/core.py wait_io_event 1 , 0 ) 
[return    ] (asyncio/core.py wait_io_event 1 , 9 ) 
[call      ] (<stdin> blink 41 , 24 ) 
[line      ] (<stdin> blink 41 , 24 ) 
[call      ] (asyncio/core.py __next__ 1 , 0 ) 
[line      ] (asyncio/core.py __next__ 1 , 0 ) 
[exception ] (asyncio/core.py __next__ 1 , 33 ) 
[line      ] (<stdin> blink 42 , 24 ) 
[call      ] (neopixel.py __setitem__ 1 , 0 ) 
[line      ] (neopixel.py __setitem__ 1 , 0 ) 
[return    ] (neopixel.py __setitem__ 1 , 22 ) 
[line      ] (<stdin> blink 43 , 24 ) 
[call      ] (neopixel.py write 1 , 0 ) 
[line      ] (neopixel.py write 1 , 0 ) 
[return    ] (neopixel.py write 1 , 12 ) 
[line      ] (<stdin> blink 44 , 36 ) 
[call      ] (asyncio/core.py sleep_ms 1 , 0 ) 
[line      ] (asyncio/core.py sleep_ms 1 , 0 ) 
[return    ] (asyncio/core.py sleep_ms 1 , 15 ) 
[call      ] (asyncio/core.py __iter__ 1 , 0 ) 
[line      ] (asyncio/core.py __iter__ 1 , 0 ) 
[return    ] (asyncio/core.py __iter__ 1 , 0 ) 
[call      ] (asyncio/core.py __next__ 1 , 0 ) 
[line      ] (asyncio/core.py __next__ 1 , 0 ) 
[return    ] (asyncio/core.py __next__ 1 , 22 ) 
[call      ] (asyncio/core.py wait_io_event 1 , 0 ) 
[line      ] (asyncio/core.py wait_io_event 1 , 0 ) 
[return    ] (asyncio/core.py wait_io_event 1 , 9 ) 
[call      ] (asyncio/core.py wait_io_event 1 , 0 ) 
[line      ] (asyncio/core.py wait_io_event 1 , 0 ) 
[return    ] (asyncio/core.py wait_io_event 1 , 9 ) 
[call      ] (<stdin> blink 41 , 24 ) 
[line      ] (<stdin> blink 41 , 24 ) 
[call      ] (asyncio/core.py __next__ 1 , 0 ) 
[line      ] (asyncio/core.py __next__ 1 , 0 ) 
[exception ] (asyncio/core.py __next__ 1 , 33 ) 
[line      ] (<stdin> blink 42 , 24 ) 
[call      ] (neopixel.py __setitem__ 1 , 0 ) 
[line      ] (neopixel.py __setitem__ 1 , 0 ) 
[return    ] (neopixel.py __setitem__ 1 , 22 ) 
[line      ] (<stdin> blink 43 , 24 ) 
[call      ] (neopixel.py write 1 , 0 ) 
[line      ] (neopixel.py write 1 , 0 ) 
[return    ] (neopixel.py write 1 , 12 ) 
[line      ] (<stdin> blink 44 , 36 ) 
[call      ] (asyncio/core.py sleep_ms 1 , 0 ) 
[line      ] (asyncio/core.py sleep_ms 1 , 0 ) 
[return    ] (asyncio/core.py sleep_ms 1 , 15 ) 
[call      ] (asyncio/core.py __iter__ 1 , 0 ) 
[line      ] (asyncio/core.py __iter__ 1 , 0 ) 
[return    ] (asyncio/core.py __iter__ 1 , 0 ) 
[call      ] (asyncio/core.py __next__ 1 , 0 ) 
[line      ] (asyncio/core.py __next__ 1 , 0 ) 
[return    ] (asyncio/core.py __next__ 1 , 22 ) 
[call      ] (asyncio/core.py wait_io_event 1 , 0 ) 
[line      ] (asyncio/core.py wait_io_event 1 , 0 ) 
[return    ] (asyncio/core.py wait_io_event 1 , 9 ) 
[call      ] (asyncio/core.py wait_io_event 1 , 0 ) 
[line      ] (asyncio/core.py wait_io_event 1 , 0 ) 
[return    ] (asyncio/core.py wait_io_event 1 , 9 ) 
[call      ] (<stdin> blink 44 , 48 ) 
[line      ] (<stdin> blink 44 , 48 ) 
[call      ] (asyncio/core.py __next__ 1 , 0 ) 
[line      ] (asyncio/core.py __next__ 1 , 0 ) 
[exception ] (asyncio/core.py __next__ 1 , 33 ) 
[call      ] (neopixel.py __len__ 1 , 0 ) 
[line      ] (neopixel.py __len__ 1 , 0 ) 
[return    ] (neopixel.py __len__ 1 , 1 ) 
[line      ] (<stdin> blink 39 , 48 ) 
[call      ] (neopixel.py __setitem__ 1 , 0 ) 
[line      ] (neopixel.py __setitem__ 1 , 0 ) 
[return    ] (neopixel.py __setitem__ 1 , 22 ) 
[line      ] (<stdin> blink 40 , 48 ) 
[call      ] (neopixel.py write 1 , 0 ) 
[line      ] (neopixel.py write 1 , 0 ) 
[return    ] (neopixel.py write 1 , 12 ) 
[line      ] (<stdin> blink 41 , 12 ) 
[call      ] (asyncio/core.py sleep_ms 1 , 0 ) 
[line      ] (asyncio/core.py sleep_ms 1 , 0 ) 
[return    ] (asyncio/core.py sleep_ms 1 , 15 ) 
[call      ] (asyncio/core.py __iter__ 1 , 0 ) 
[line      ] (asyncio/core.py __iter__ 1 , 0 ) 
[return    ] (asyncio/core.py __iter__ 1 , 0 ) 
[call      ] (asyncio/core.py __next__ 1 , 0 ) 
[line      ] (asyncio/core.py __next__ 1 , 0 ) 
[return    ] (asyncio/core.py __next__ 1 , 22 ) 
[call      ] (asyncio/core.py wait_io_event 1 , 0 ) 
[line      ] (asyncio/core.py wait_io_event 1 , 0 ) 
[return    ] (asyncio/core.py wait_io_event 1 , 9 ) 
[call      ] (asyncio/core.py wait_io_event 1 , 0 ) 
[line      ] (asyncio/core.py wait_io_event 1 , 0 ) 
[return    ] (asyncio/core.py wait_io_event 1 , 9 ) 
[call      ] (<stdin> a_main 49 , 42 ) 
[line      ] (<stdin> a_main 49 , 42 ) 
[call      ] (asyncio/core.py __next__ 1 , 0 ) 
[line      ] (asyncio/core.py __next__ 1 , 0 ) 
[exception ] (asyncio/core.py __next__ 1 , 33 ) 
[return    ] (<stdin> a_main 49 , 42 ) 
[exception ] (asyncio/core.py run_until_complete 1 , 111 ) 
[return    ] (asyncio/core.py run_until_complete 1 , 324 ) 
[return    ] (asyncio/core.py run 1 , 7 ) 
[return    ] (<stdin> call_frozen 56 , 37 ) 
========================================

crashlog

mpremote run src/check_settrace.py 
========================================
Tracing normal function
========================================
[call      ] (<stdin> call_frozen 52 , 0 ) 
             OrderedDict({})
[line      ] (<stdin> call_frozen 53 , 0 ) 
             OrderedDict({})
[call      ] (neopixel.py __len__ 1 , 0 ) 
             OrderedDict({})
[line      ] (neopixel.py __len__ 1 , 0 ) 
             OrderedDict({})
[return    ] (neopixel.py __len__ 1 , 1 ) 
             OrderedDict({})
[line      ] (<stdin> call_frozen 54 , 0 ) 
             OrderedDict({})
[call      ] (neopixel.py fill 1 , 0 ) 
             OrderedDict({})
[line      ] (neopixel.py fill 1 , 0 ) 
             OrderedDict({})
[return    ] (neopixel.py fill 1 , 27 ) 
             OrderedDict({})
[line      ] (<stdin> call_frozen 55 , 10 ) 
             OrderedDict({})
[call      ] (neopixel.py write 1 , 0 ) 
             OrderedDict({})
[line      ] (neopixel.py write 1 , 0 ) 
             OrderedDict({})
[return    ] (neopixel.py write 1 , 12 ) 
             OrderedDict({})
[line      ] (<stdin> call_frozen 56 , 17 ) 
             OrderedDict({})
****************************************
[line      ] (<stdin> call_frozen 57 , 26 ) 
             OrderedDict({})
[call      ] (asyncio/core.py run 1 , 0 ) 
             OrderedDict({})
[line      ] (asyncio/core.py run 1 , 0 ) 
             OrderedDict({})
[call      ] (asyncio/core.py create_task 1 , 0 ) 
             OrderedDict({})
[line      ] (asyncio/core.py create_task 1 , 0 ) 
             OrderedDict({})
[return    ] (asyncio/core.py create_task 1 , 31 ) 
             OrderedDict({'__main__': <Task>})
[call      ] (asyncio/core.py run_until_complete 1 , 0 ) 
             OrderedDict({})
[line      ] (asyncio/core.py run_until_complete 1 , 0 ) 
             OrderedDict({})
[call      ] (asyncio/core.py wait_io_event 1 , 0 ) 
             OrderedDict({})
[line      ] (asyncio/core.py wait_io_event 1 , 0 ) 
             OrderedDict({})
[return    ] (asyncio/core.py wait_io_event 1 , 9 ) 
             OrderedDict({})
[call      ] (<stdin> a_main 47 , 0 ) 
             OrderedDict({})
[line      ] (<stdin> a_main 48 , 0 ) 
             OrderedDict({})
[call      ] (asyncio/core.py create_task 1 , 0 ) 
             OrderedDict({})
[line      ] (asyncio/core.py create_task 1 , 0 ) 
             OrderedDict({})
[return    ] (asyncio/core.py create_task 1 , 31 ) 
             OrderedDict({'__main__': <Task>})
[line      ] (<stdin> a_main 49 , 12 ) 
             OrderedDict({})
[call      ] (asyncio/core.py create_task 1 , 0 ) 
             OrderedDict({})
[line      ] (asyncio/core.py create_task 1 , 0 ) 
             OrderedDict({})
[return    ] (asyncio/core.py create_task 1 , 31 ) 
             OrderedDict({'__main__': <Task>})
[line      ] (<stdin> a_main 50 , 27 ) 
             OrderedDict({})
[call      ] (asyncio/core.py sleep_ms 1 , 0 ) 
             OrderedDict({})
[line      ] (asyncio/core.py sleep_ms 1 , 0 ) 
             OrderedDict({})
[return    ] (asyncio/core.py sleep_ms 1 , 15 ) 
             OrderedDict({
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

MPY version : v1.26.0-preview.272.ga7c7a75eef.dirty on 2025-06-18
IDF version : v5.2.2
Machine     : Generic ESP32 module with ESP32

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

Core  1 register dump:
PC      : 0x400fe7c3  PS      : 0x00060230  A0      : 0x800fc0ca  A1      : 0x3ffcea90  
A2      : 0xbd083bdc  A3      : 0x3ffceab0  A4      : 0x00000000  A5      : 0x3ffceac0  
A6      : 0x00000000  A7      : 0x00000001  A8      : 0x00000000  A9      : 0x3ffd04c0  
A10     : 0x3ffd0550  A11     : 0x3ffbc988  A12     : 0x00000001  A13     : 0x3ffceac0  
A14     : 0x00000000  A15     : 0x3ffbc984  SAR     : 0x0000001a  EXCCAUSE: 0x0000001c  
EXCVADDR: 0xbd083bf4  LBEG    : 0x400f6678  LEND    : 0x400f6687  LCOUNT  : 0x00000003  


Backtrace: 0x400fe7c0:0x3ffcea90 0x400fc0c7:0x3ffceab0 0x400f4c5d:0x3ffceaf0 0x400f664b:0x3ffceb10 0x400f4c5d:0x3ffceb40 0x40128ddd:0x3ffceb60 0x400f7959:0x3ffceba0 0x400fef35:0x3ffcebd0 0x400856bd:0x3ffcebf0 0x400f7903:0x3ffcec90 0x400fef35:0x3ffcecc0 0x4012eac0:0x3ffcece0 0x4012f087:0x3ffced10 0x400840f1:0x3ffced40 0x400f7903:0x3ffcede0 0x400fef35:0x3ffcee50 0x400feff9:0x3ffcee70 0x4008584d:0x3ffcee90 0x400f7c6f:0x3ffcef30 0x400f7d61:0x3ffcef50 0x400f77fa:0x3ffcef80 0x400fef35:0x3ffcefa0 0x400feff9:0x3ffcefc0 0x4008584d:0x3ffcefe0 0x400f7903:0x3ffcf080 0x400fef35:0x3ffcf0b0 0x400856bd:0x3ffcf0d0 0x400f7903:0x3ffcf170 0x400fef35:0x3ffcf1d0 0x400feff9:0x3ffcf1f0 0x4008584d:0x3ffcf210 0x400f7903:0x3ffcf2b0 0x400fef35:0x3ffcf310 0x400856bd:0x3ffcf330 0x400f7903:0x3ffcf3d0 0x400fef35:0x3ffcf440 0x400fef4a:0x3ffcf460 0x4010d263:0x3ffcf480 0x4010d3a6:0x3ffcf510 0x400f048a:0x3ffcf560




ELF file SHA256: a3c1413ff

def do_connect():
import machine, network
wlan = network.WLAN()
wlan.active(True)
if not wlan.isconnected():
print('connecting to network...')
wlan.connect('IoT', 'MicroPython')
while not wlan.isconnected():
machine.idle()
print('network config:', wlan.ipconfig('addr4'))
do_connect()

A quick test to check if @micropython@native/viper code triggers settrace events.

import sys
import json
from collections import OrderedDict as OD

def tracer(frame, event, arg):
    try:
        print(f"[{event:<10}] ({frame.f_lineno} , {frame.f_lasti} ) ", end="")
        f_locals = OD(sorted(frame.f_locals.items()))
        print(f"{f_locals}")
    except Exception as e:
        pass
    return tracer


@micropython.native
def f_native(n):
    i = 0
    while i < n:
        print(i)
        i += 1

@micropython.viper
def f_native(n:int):
    i = 0
    while i < n:
        print(i)
        i += 1

def f_normal(n):
    i = 0
    while i < n:
        print(i)
        i += 1

print("="*40)
print("Tracing @micropython.native")

sys.settrace(tracer)
f_native(4)
sys.settrace(None)
print("="*40)

print("="*40)
print("Tracing @micropython.viper")
print("="*40)
sys.settrace(tracer)
f_native(4)
sys.settrace(None)
print("="*40)

print("="*40)
print("Tracing normal function")
print("="*40)
sys.settrace(tracer)
f_normal(4)
sys.settrace(None)
print("="*40)

Shows that neither @micropython.native nor @micropython.viper code triggers settrace events, while a normal function does:

========================================
Tracing @micropython.native
========================================
0
1
2
3
========================================
========================================
Tracing @micropython.viper
========================================
0
1
2
3
========================================
========================================
Tracing normal function
========================================
[call      ] (29 , 0 ) OrderedDict({})
[line      ] (30 , 0 ) OrderedDict({})
[line      ] (30 , 0 ) OrderedDict({})
[line      ] (31 , 0 ) OrderedDict({'i': 0})
[line      ] (33 , 0 ) OrderedDict({'i': 0})
[line      ] (33 , 0 ) OrderedDict({'i': 0})
[line      ] (33 , 0 ) OrderedDict({'i': 0})
[line      ] (33 , 0 ) OrderedDict({'i': 0})
[line      ] (32 , 0 ) OrderedDict({'i': 0})
[line      ] (32 , 0 ) OrderedDict({'i': 0})
[line      ] (32 , 0 ) OrderedDict({'i': 0})
0
[line      ] (32 , 7 ) OrderedDict({'i': 0})
[line      ] (33 , 7 ) OrderedDict({'i': 0})
[line      ] (33 , 7 ) OrderedDict({'i': 0})
[line      ] (33 , 7 ) OrderedDict({'i': 0})
[line      ] (33 , 7 ) OrderedDict({'i': 0})
[line      ] (33 , 7 ) OrderedDict({'i': 1})
[line      ] (33 , 7 ) OrderedDict({'i': 1})
[line      ] (33 , 7 ) OrderedDict({'i': 1})
[line      ] (33 , 7 ) OrderedDict({'i': 1})
[line      ] (32 , 7 ) OrderedDict({'i': 1})
[line      ] (32 , 7 ) OrderedDict({'i': 1})
[line      ] (32 , 7 ) OrderedDict({'i': 1})
1
[line      ] (32 , 7 ) OrderedDict({'i': 1})
[line      ] (33 , 7 ) OrderedDict({'i': 1})
[line      ] (33 , 7 ) OrderedDict({'i': 1})
[line      ] (33 , 7 ) OrderedDict({'i': 1})
[line      ] (33 , 7 ) OrderedDict({'i': 1})
[line      ] (33 , 7 ) OrderedDict({'i': 2})
[line      ] (33 , 7 ) OrderedDict({'i': 2})
[line      ] (33 , 7 ) OrderedDict({'i': 2})
[line      ] (33 , 7 ) OrderedDict({'i': 2})
[line      ] (32 , 7 ) OrderedDict({'i': 2})
[line      ] (32 , 7 ) OrderedDict({'i': 2})
[line      ] (32 , 7 ) OrderedDict({'i': 2})
2
[line      ] (32 , 7 ) OrderedDict({'i': 2})
[line      ] (33 , 7 ) OrderedDict({'i': 2})
[line      ] (33 , 7 ) OrderedDict({'i': 2})
[line      ] (33 , 7 ) OrderedDict({'i': 2})
[line      ] (33 , 7 ) OrderedDict({'i': 2})
[line      ] (33 , 7 ) OrderedDict({'i': 3})
[line      ] (33 , 7 ) OrderedDict({'i': 3})
[line      ] (33 , 7 ) OrderedDict({'i': 3})
[line      ] (33 , 7 ) OrderedDict({'i': 3})
[line      ] (32 , 7 ) OrderedDict({'i': 3})
[line      ] (32 , 7 ) OrderedDict({'i': 3})
[line      ] (32 , 7 ) OrderedDict({'i': 3})
3
[line      ] (32 , 7 ) OrderedDict({'i': 3})
[line      ] (33 , 7 ) OrderedDict({'i': 3})
[line      ] (33 , 7 ) OrderedDict({'i': 3})
[line      ] (33 , 7 ) OrderedDict({'i': 3})
[line      ] (33 , 7 ) OrderedDict({'i': 3})
[line      ] (33 , 7 ) OrderedDict({'i': 4})
[line      ] (33 , 7 ) OrderedDict({'i': 4})
[line      ] (33 , 7 ) OrderedDict({'i': 4})
[line      ] (33 , 7 ) OrderedDict({'i': 4})
[line      ] (33 , 7 ) OrderedDict({'i': 4})
[line      ] (33 , 7 ) OrderedDict({'i': 4})
[return    ] (33 , 7 ) OrderedDict({'i': 4})
========================================

MicroPython Debugger Support Documentation

This document describes the enhanced debugging functionality added to MicroPython through two key commits that implement comprehensive local variable introspection for the sys.settrace() functionality.

Overview

The enhancements enable debuggers and profiling tools to access local variable names and values in function frames, which is essential for interactive debugging, breakpoint inspection, and runtime analysis. The implementation includes both basic local variable access and advanced variable name preservation with correct slot mapping.

Key Features Added

1. Basic Frame Local Variables Access (frame.f_locals)

The initial implementation provides access to local variables in stack frames through the frame.f_locals property. This foundational feature enables debuggers to inspect variable values at runtime.

Key Components:

  • frame_f_locals() function - Core implementation in py/profile.c
  • Memory-safe access - Includes GC lock checking and state validation
  • Generic fallback names - Uses local_01, local_02, etc. when variable names unavailable
  • Robust error handling - Graceful handling of invalid state or memory allocation failures

Implementation Features:

  • Pre-allocates dictionary size based on frame state count for efficiency
  • Validates code state and state array before access
  • Skips NULL values in the state array
  • Safe qstr creation with error checking

2. Advanced Variable Name Storage (MICROPY_SAVE_LOCAL_VARIABLE_NAMES)

An enhanced compile-time feature that preserves actual local variable names in compiled bytecode for professional debugging capabilities.

Configuration:

#define MICROPY_SAVE_LOCAL_VARIABLE_NAMES (1)  // Save local variable names for debugging

Note: The implementation also supports a conditional flag MICROPY_PY_SYS_SETTRACE_SAVE_NAMES for more granular control when MICROPY_PY_SYS_SETTRACE is enabled.

Components:

  • py/localnames.h - Data structures for variable name mapping
  • py/localnames.c - Functions to manage variable name storage and retrieval
  • py/debug_locals.h - Debug utilities header
  • py/debug_locals.c - Debug utilities implementation

3. Enhanced Frame Local Variables Access with Real Names

Enhanced the frame.f_locals property to return actual local variable names and values instead of generic placeholder names.

Evolution:

# Basic implementation returns:
{'local_01': value1, 'local_02': value2, ...}

# Enhanced implementation returns:
{'foo': 'hello debugger', 'bar': 123, 'my_var': [1, 2, 3], ...}

4. Reverse Slot Assignment Fix

Fixed a critical bug in variable-to-slot mapping where variables were incorrectly mapped to runtime slots.

Problem: Variables were being assigned sequentially from low slots up, but the runtime was expecting them from high slots down.

Solution: Implemented reverse slot assignment where:

  • First variable in source order → Highest available slot
  • Last variable in source order → Lowest available slot (after parameters)

5. Debug Configuration Options

Added optional compiler optimization controls for enhanced debugging experience:

// Disable compiler optimizations for debugging (optional)
#define MICROPY_COMP_CONST                 (0)
#define MICROPY_COMP_MODULE_CONST          (0)
#define MICROPY_COMP_DOUBLE_TUPLE_ASSIGN   (0)
#define MICROPY_COMP_TRIPLE_TUPLE_ASSIGN   (0)

Data Structures

mp_local_names_t

typedef struct _mp_local_names_t {
    uint16_t num_locals;                      // Total number of local variables with names
    qstr local_names[MICROPY_PY_SYS_SETTRACE_NAMES_MAX];     // Array of variable names, indexed by local_num
    uint16_t local_nums[MICROPY_PY_SYS_SETTRACE_NAMES_MAX];  // Reverse mapping: name index → local_num
    uint16_t order_count;                     // Number of variables stored in order they were defined
    uint16_t runtime_slots[MICROPY_PY_SYS_SETTRACE_NAMES_MAX]; // Mapping of local_num to runtime slots
} mp_local_names_t;

Note: MICROPY_PY_SYS_SETTRACE_NAMES_MAX defaults to 32 and can be configured at compile time.

Enhanced mp_raw_code_t

typedef struct _mp_raw_code_t {
    // ... existing fields ...
    #if MICROPY_PY_SYS_SETTRACE_SAVE_NAMES
    mp_local_names_t local_names;  // Maps local variable indices to names
    #endif
} mp_raw_code_t;

API Functions

Core Functions

  • mp_local_names_init() - Initialize local names structure
  • mp_local_names_add() - Add variable name mapping
  • mp_local_names_get_name() - Get variable name by index
  • mp_local_names_get_local_num() - Get local number by order index
  • mp_local_names_get_runtime_slot() - Get runtime slot mapping

Debug Functions

  • mp_debug_print_local_variables() - Print variable mappings for debugging
  • mp_debug_locals_info() - Exposed as sys.debug_locals_info() for runtime debugging

Compilation Integration

Enhanced Compiler (py/compile.c)

#if MICROPY_PY_SYS_SETTRACE_SAVE_NAMES
// Save the local variable names in the raw_code for debugging
if (SCOPE_IS_FUNC_LIKE(scope->kind) && scope->num_locals > 0) {
    // Populate with variable names - examining the assignment order
    for (int i = 0; i < scope->id_info_len; i++) {
        id_info_t *id = &scope->id_info[i];
        if ((id->kind == ID_INFO_KIND_LOCAL || id->kind == ID_INFO_KIND_CELL) && 
            id->local_num < scope->num_locals && 
            id->local_num < MICROPY_PY_SYS_SETTRACE_NAMES_MAX) {
            
            mp_local_names_add(&scope->raw_code->local_names, id->local_num, id->qst);
        }
    }
}
#endif

Frame Locals Implementation (py/profile.c)

The implementation of frame_f_locals() has evolved through two phases:

Phase 1: Basic Implementation

The initial version provides fundamental local variable access:

static mp_obj_t frame_f_locals(mp_obj_t self_in) {
    // Memory safety: Cannot create locals dict when GC is locked
    if (gc_is_locked()) {
        return MP_OBJ_NULL;
    }
    
    mp_obj_frame_t *frame = MP_OBJ_TO_PTR(self_in);
    mp_obj_dict_t *locals_dict = mp_obj_new_dict(frame->code_state->n_state);
    const mp_code_state_t *code_state = frame->code_state;

    // Validate state array before access
    if (code_state == NULL || code_state->state == NULL) {
        return MP_OBJ_FROM_PTR(locals_dict);
    }

    // Generate generic variable names for all non-NULL values
    for (size_t i = 0; i < code_state->n_state; ++i) {
        if (code_state->state[i] == NULL) {
            continue; // Skip invalid values
        }
        
        char var_name[16];
        snprintf(var_name, sizeof(var_name), "local_%02d", (int)(i + 1));
        qstr var_name_qstr = qstr_from_str(var_name);
        
        if (var_name_qstr == MP_QSTR_NULL) {
            continue; // Skip if qstr creation fails
        }
        
        mp_obj_dict_store(locals_dict, MP_OBJ_NEW_QSTR(var_name_qstr), code_state->state[i]);
    }
    
    return MP_OBJ_FROM_PTR(locals_dict);
}

Phase 2: Enhanced Implementation

The enhanced version adds comprehensive variable name support:

  1. Handles Parameters - Maps function arguments to correct slots (0 to n_args-1)
  2. Implements Reverse Slot Assignment - Maps local variables using reverse order
  3. Provides Fallback - Uses generic names when variable names unavailable
  4. Validates State - Ensures safe access to frame state arrays
// REVERSE SLOT ASSIGNMENT: Variables assigned from highest available slot down
uint16_t total_locals = code_state->n_state;
uint16_t reverse_slot = total_locals - 1 - order_idx;

Key Safety Features

  • GC Lock Protection: Prevents dictionary creation during garbage collection
  • State Validation: Checks for NULL code_state and state arrays
  • Memory Allocation Checking: Validates qstr creation before use
  • NULL Value Skipping: Ignores uninitialized or cleared variables

Configuration Changes

Unix Standard Port (ports/unix/variants/standard/mpconfigvariant.h)

The configuration enables the debugging features. The current implementation uses different configuration options:

// Enable sys.settrace support (required)
#define MICROPY_PY_SYS_SETTRACE (1)

// Enable local variable name preservation (currently commented out by default)
// #define MICROPY_PY_SYS_SETTRACE_SAVE_NAMES (1)

// Alternative configuration for broader variable name support
#define MICROPY_SAVE_LOCAL_VARIABLE_NAMES (1)

// Optional: Disable compiler optimizations for debugging
#define MICROPY_COMP_CONST                 (0)
#define MICROPY_COMP_MODULE_CONST          (0)
#define MICROPY_COMP_DOUBLE_TUPLE_ASSIGN   (0)
#define MICROPY_COMP_TRIPLE_TUPLE_ASSIGN   (0)

// Optional: Enable verbose debug output
#define MICROPY_DEBUG_VERBOSE              (0)

Configuration Levels:

  1. Basic Debugging (minimum):

    #define MICROPY_PY_SYS_SETTRACE (1)
    • Enables sys.settrace() with generic variable names
    • frame.f_locals returns {'local_01': value1, 'local_02': value2, ...}
  2. Enhanced Debugging (settrace-specific):

    #define MICROPY_PY_SYS_SETTRACE (1)
    #define MICROPY_PY_SYS_SETTRACE_SAVE_NAMES (1)
    • Enables sys.settrace() with actual variable names using settrace-specific storage
    • Conditional compilation only when settrace is enabled
  3. Advanced Debugging (broader support):

    #define MICROPY_PY_SYS_SETTRACE (1)
    #define MICROPY_SAVE_LOCAL_VARIABLE_NAMES (1)
    • Enables variable name preservation across broader MicroPython functionality
    • frame.f_locals returns {'foo': value1, 'bar': value2, ...}
  4. Debug-Optimized Build (for intensive debugging):

    #define MICROPY_PY_SYS_SETTRACE (1)
    #define MICROPY_SAVE_LOCAL_VARIABLE_NAMES (1)
    #define MICROPY_COMP_CONST (0)
    #define MICROPY_COMP_MODULE_CONST (0)
    #define MICROPY_COMP_DOUBLE_TUPLE_ASSIGN (0)
    #define MICROPY_COMP_TRIPLE_TUPLE_ASSIGN (0)
    • All debugging features enabled
    • Compiler optimizations disabled for more predictable variable behavior

Global Configuration (py/mpconfig.h)

// If not explicitly enabled, disable settrace variable name saving
#ifndef MICROPY_PY_SYS_SETTRACE
#define MICROPY_PY_SYS_SETTRACE_SAVE_NAMES (0)
#endif
#ifndef MICROPY_PY_SYS_SETTRACE_SAVE_NAMES
#define MICROPY_PY_SYS_SETTRACE_SAVE_NAMES (0)
#endif

Note: The broader MICROPY_SAVE_LOCAL_VARIABLE_NAMES configuration is not currently implemented in the global config, but is used at the implementation level.

Build System Integration

Makefile Changes (py/py.mk)

PY_CORE_O_BASENAME = $(addprefix py/,\
    # ... existing files ...
    localnames.o \
    debug_locals.o \
    # ... rest of files ...

Note: The build system automatically includes the new object files for local variable name support and debug utilities.

Usage Examples

Basic sys.settrace Usage

import sys
from collections import OrderedDict

def tracer(frame, event, arg):
    if event == 'line':
        print(f"Line {frame.f_lineno}: {frame.f_locals}")
    return tracer

def test_function():
    foo = "hello debugger"
    bar = 123
    my_list = [1, 2, 3]
    print("Debug me!")

sys.settrace(tracer)
test_function()
sys.settrace(None)

Expected Output

Line 11: {'foo': 'hello debugger'}
Line 12: {'foo': 'hello debugger', 'bar': 123}
Line 13: {'foo': 'hello debugger', 'bar': 123, 'my_list': [1, 2, 3]}
Debug me!
Line 14: {'foo': 'hello debugger', 'bar': 123, 'my_list': [1, 2, 3]}

Debug Output

When MICROPY_PY_SYS_SETTRACE_SAVE_NAMES is enabled, detailed debug information is printed:

DEBUG: Processing frame with 16 state slots
DEBUG: Parameters: 0 positional + 0 keyword-only = 0 total
DEBUG: Variable 'foo' (order 0) -> REVERSE slot 15
SUCCESS: 'foo' mapped to state[15] = 'hello debugger'
DEBUG: Variable 'bar' (order 1) -> REVERSE slot 14  
SUCCESS: 'bar' mapped to state[14] = 123

Memory Considerations

  • Slot Limit: Maximum of MICROPY_PY_SYS_SETTRACE_NAMES_MAX (32) local variables per function
  • Memory Overhead: Approximately 4 bytes per local variable name mapping
  • Compile-time: Only enabled when MICROPY_PY_SYS_SETTRACE_SAVE_NAMES is defined

Compatibility

Backward Compatibility

  • When MICROPY_PY_SYS_SETTRACE_SAVE_NAMES is disabled (default), basic functionality still works
  • When only MICROPY_PY_SYS_SETTRACE is enabled, frame.f_locals uses generic names (local_01, local_02, etc.)
  • Existing code continues to work without modification
  • Progressive enhancement: more features become available as more flags are enabled

Platform Support

  • Currently tested on Unix standard port
  • Should work on any platform with sys.settrace() support
  • Requires MICROPY_PERSISTENT_CODE_SAVE to be enabled

Future Enhancements

This implementation provides the foundation for:

  • Interactive Debuggers - Step-through debugging with variable inspection
  • Profiling Tools - Performance analysis with variable tracking
  • IDE Integration - Real-time variable watches and inspection
  • Remote Debugging - Debug MicroPython over network connections

Testing

A test script test_settrace.py is included that demonstrates:

  • Variable name preservation
  • Correct slot assignment
  • Frame-by-frame variable tracking
  • Mixed data type handling

Debugging Commands

Runtime Debug Information

import sys
sys.debug_locals_info()  # Print detailed variable mapping information

This command prints comprehensive information about:

  • Current code state and frame details
  • Variable name to slot mappings
  • Complete state array contents
  • Parameter vs. local variable assignments

Conclusion

These enhancements represent a significant advancement in MicroPython's debugging capabilities, implemented through a progressive approach:

  1. Foundation: Basic frame.f_locals access with memory safety and error handling
  2. Enhancement: Advanced variable name preservation and correct slot mapping
  3. Optimization: Optional compiler settings for debug-friendly builds

The implementation enables professional-grade debugging tools while maintaining backward compatibility and providing configurable levels of debugging support. The reverse slot assignment fix resolves a fundamental issue that was preventing proper variable inspection, making the development experience much more productive for Python developers working with MicroPython.

# simple dockerfile for micropython with debugpy
# Build
# docker build -t micropython/debugpy:0.2 .
# Usage
# To run the container with debugpy enabled , you can mount the following directories
# to the default sys.path[] locations
# - the source code directory
# - the launcher directory
# - the debugpy library directory
# docker run -it --rm -p 5678:5678 -v ./src:/usr/micropython -v ./launcher:/usr/lib/micropython -v ./python-ecosys/debugpy:/root/.micropython/lib micropython/debugpy:0.2 -m start_debugpy
FROM ubuntu:24.04
RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates
#default directorie will be the ./src folder
WORKDIR /usr/micropython
COPY firmware/unix_debug_enabled/micropython /usr/local/bin/micropython
ENTRYPOINT ["/usr/local/bin/micropython"]
# default debug port
EXPOSE 5678

Test notes for debugpy

Todo:

  • function scope: also save the names of the parameters to a function, so they can been inspected in the debugger. Likely an extention of the MICROPY_SAVE_LOCAL_VARIABLE_NAMES feature.

  • handle 'exceptions' that are emitted via sys.settrace() and send the correstponding events through DAP ✅ they are emitted by sys.settrace as exception events , but the debugger does not handle them yet.

a = 1; b=2

Is the cache preventing hitting a 2nd breakpoint ? https://vscode.dev/github/Josverl/micropython/blob/settrace_tests/py/profile.c#L447-L462

// Cache the last calculated line number to avoid redundant calls
static size_t cached_line_no = 0;
static const byte *cached_ip = NULL;

if (code_state->ip != cached_ip) {
    cached_ip = code_state->ip;
    cached_line_no = mp_prof_bytecode_lineno(rc, code_state->ip - prelude->opcodes);
}

size_t current_line_no = cached_line_no;
if (prev_line_no != current_line_no) {
    args->frame->lineno = current_line_no;
    args->event = MP_OBJ_NEW_QSTR(MP_QSTR_line);
    top = mp_prof_callback_invoke(callback, args);
}

send terminated event at end of debugging session

https://microsoft.github.io/debug-adapter-protocol/specification#Events_Terminated

interface TerminatedEvent extends Event {
  event: 'terminated';

  body?: {
    /**
     * A debug adapter may set `restart` to true (or to an arbitrary object) to
     * request that the client restarts the session.
     * The value is not interpreted by the client and passed unmodified as an
     * attribute `__restart` to the `launch` and `attach` requests.
     */
    restart?: any;
  };
}

things to reserach

MICROPY_EMIT_BYTECODE_USES_QSTR_TABLE

  • is it possible / usefull to disable the tracing on the few bottom frames that are actually the debug lancher ?

  • also tmay het to avoid tracing the mpremote injected vfs code ?

  • what does setting the env MICROPYINSPECT do ?

// The settrace feature requires that we maintain additional metadata on the raw // code object which is normally only done when writing .mpy files. #error "MICROPY_PY_SYS_SETTRACE requires MICROPY_PERSISTENT_CODE_SAVE to be enabled"

# py/profile.c#L474
// SETTRACE event OPCODE
// TODO: frame.f_trace_opcodes=True
if (false) {
    args->event = MP_OBJ_NEW_QSTR(MP_QSTR_opcode);
}

#if MICROPY_STACKLESS ? do we need to worry about that as well

Activating tracing directy on the current frame , rather than in subsequent frames can be done in CPython: it’s possible to set a trace function by assigning frame.f_trace = tracefunc explicitly, rather than relying on it being set indirectly via the return value from an already installed trace function. This is also required for activating the trace function on the current frame, which settrace() doesn’t do. Note that in order for this to work, a global tracing function must have been installed with settrace() in order to enable the runtime tracing machinery, but it doesn’t need to be the same tracing function (e.g. it could be a low overhead tracing function that simply returns None to disable itself immediately on each frame).

micropython's code object

py/objcode: Factor code object out into its own file.

The mp_obj_code_t and mp_type_code code object was defined internally in both py/builtinevex.c and py/profile.c, with completely different implementations (the former very minimal, the latter quite complete).

This commit factors these implementations into a new, separate source file, and allows the code object to have four different modes, selected at compile-time:

  • MICROPY_PY_BUILTINS_CODE_NONE: code object not included in the build.

  • MICROPY_PY_BUILTINS_CODE_MINIMUM: very simple code object that just holds a reference to the function that it represents. This level is used when MICROPY_PY_BUILTINS_COMPILE is enabled.

  • MICROPY_PY_BUILTINS_CODE_BASIC: simple code object that holds a reference to the proto-function and its constants.

  • MICROPY_PY_BUILTINS_CODE_FULL: almost complete implementation of the code object. This level is used when MICROPY_PY_SYS_SETTRACE is enabled.

tracing opcodes - python 3.7

event: 'opcode' The interpreter is about to execute a new opcode (see dis for opcode details). The local trace function is called; arg is None; the return value specifies the new local trace function. Per-opcode events are not emitted by default: they must be explicitly requested by setting f_trace_opcodes to True on the frame.

py/profile.c#L474-L479
// SETTRACE event OPCODE
// TODO: frame.f_trace_opcodes=True
if (false) {
    args->event = MP_OBJ_NEW_QSTR(MP_QSTR_opcode);
}

VSCode Debugger extention: can create a seperate detail/debugger vieew that shows the opcodes executed in the current frame.

local variable order :

// fastn has items in reverse order (fastn[0] is local[0], fastn[-1] is local[1], etc)

  • MP_BC_LOAD_FAST_N
  • MP_BC_STORE_FAST_N

rp2 / esp32 difference

onthe rp2 build not all the frame local variables seem to be available inlcuding the frame?

(.venv) jos@josverl-sb5:~/micropython$ mpremote connect /dev/ttyUSB0 run tests/sys_settrace/sys_settrace_breakpoint.py > rp2.out
(.venv) jos@josverl-sb5:~/micropython$ mpremote connect /dev/ttyACM0 run tests/sys_settrace/sys_settrace_breakpoint.py > esp.out
Starting sys.settrace tests for breakpoints and frame access
TRACE call: Entering test_function

*** BREAKPOINT HIT ***
Event: line
Function: test_function
Line: 80
Last instruction: 0
TRACE return: Leaving test_function with value: None
Test function result: 35633
Breakpoint was hit: True
Testing stack frames with nested calls:
TRACE call: Entering nested_function
TRACE call: Entering nested_function
TRACE call: Entering nested_function

*** STACK FRAMES ***

Current frame:
  Name: nested_function
  Line: 93
  Local variables:
TRACE call: Entering simplified_repr
TRACE return: Leaving simplified_repr with value: None
<     local_1 = 0
---
>     current_frame = <object>
28c28
<     local_14 = 93
---
>     key = 93
31,43c31
<     local_15 = '  Line: {}'
< TRACE call: Entering simplified_repr
< TRACE return: Leaving simplified_repr with value: None
<     local_16 = <object>
< TRACE call: Entering simplified_repr
< TRACE return: Leaving simplified_repr with value: None
<     local_17 = <function>
< TRACE call: Entering simplified_repr
< TRACE return: Leaving simplified_repr with value: None
<     local_2 = <object>
< TRACE call: Entering simplified_repr
< TRACE return: Leaving simplified_repr with value: None
<     local_3 = <object>
---
>     parent_frame = <object>
51,57c39
<     local_1 = 1
< TRACE call: Entering simplified_repr
< TRACE return: Leaving simplified_repr with value: None
<     local_15 = 1
< TRACE call: Entering simplified_repr
< TRACE return: Leaving simplified_repr with value: None
<     local_16 = 0
---
>     current_frame = 0
60c42
<     local_17 = <object>
---
>     level_data = 'data at level 1'
63c45
<     local_6 = 'data at level 1'
---
>     level_number = 10
66c48
<     local_7 = 10
---
>     parent_frame = 1

debugging in simple docker container

Debugging the debugger

Howto debug debugpy itself, also includes logging.

Debugging Debugpy.

The easiest thing to do is turn on logging for debugpy with some environment variables:

Environment Variable Value
PYDEVD_DEBUG true
DEBUGPY_LOG_DIR directory that already exists
PYDEVD_DEBUG_FILE directory that already exists - use same as previous variable

Launch scripts for ESP32

During development - make sure the files are on the device, and then run the script to start the debugpy server.

mpremote cp target.py : + cp -r /home/jos/micropython/micropython-lib/python-ecosys/debugpy/debugpy :/ + soft-reset + run start_debugpy_esp32.py
$ mpremote cp target.py : + cp -r /home/jos/micropython/micropython-lib/python-ecosys/debugpy/debugpy :/ + run start_debugpy_esp32.py 
cp target.py :
Up to date: ./target.py
cp /home/jos/micropython/micropython-lib/python-ecosys/debugpy/debugpy :/
Up to date: ./debugpy/__init__.py
Up to date: ./debugpy/common/__init__.py
Up to date: ./debugpy/common/constants.py
Up to date: ./debugpy/common/messaging.py
Up to date: ./debugpy/public_api.py
Up to date: ./debugpy/server/__init__.py
Up to date: ./debugpy/server/debug_session.py
Up to date: ./debugpy/server/pdb_adapter.py

 _____  _______ ______ _______ _______ ______ ___ ___ 
|     \|    ___|   __ \   |   |     __|   __ \   |   |
|  --  |    ___|   __ <   |   |    |  |    __/\     / 
|_____/|_______|______/_______|_______|___|    |___|  

MicroPython VS Code Debugging Test
==================================
Debugpy listening on 192.168.1.25:5678

ESP32 Build

  • needed to update py.cmake to include the localnames in the source files list
  • perhaps the file should have a different name (settrace_names ?), or the functions should be included in one of the other files
    ${MICROPY_PY_DIR}/lexer.c
    ${MICROPY_PY_DIR}/localnames.c
    ${MICROPY_PY_DIR}/malloc.c

Suport for viewing the state of local variables in the debugger

1. Basic Frame Local Variables Access (frame.f_locals)

The initial implementation provides access to local variables in stack frames through the frame.f_locals property. This foundational feature enables debuggers to inspect variable values at runtime.

Key Components:

  • frame_f_locals() function - Core implementation in py/profile.c
  • Memory-safe access - Includes GC lock checking and state validation
  • Generic fallback names - Uses local_01, local_02, etc. when variable names unavailable
  • Robust error handling - Graceful handling of invalid state or memory allocation failures

Implementation Features:

  • Pre-allocates dictionary size based on frame state count for efficiency
  • Validates code state and state array before access
  • Skips NULL values in the state array
  • Safe qstr creation with error checking

2. Advanced Variable Name Storage (MICROPY_SAVE_LOCAL_VARIABLE_NAMES)

An enhanced compile-time feature that preserves actual local variable names in compiled bytecode for professional debugging capabilities.

Configuration:

#define MICROPY_SAVE_LOCAL_VARIABLE_NAMES (1)  // Save local variable names for debugging

Note: The implementation also supports a conditional flag MICROPY_PY_SYS_SETTRACE_SAVE_NAMES for more granular control when MICROPY_PY_SYS_SETTRACE is enabled.

Components:

  • py/localnames.h - Data structures for variable name mapping
  • py/localnames.c - Functions to manage variable name storage and retrieval
  • py/debug_locals.h - Debug utilities header
  • py/debug_locals.c - Debug utilities implementation

3. Enhanced Frame Local Variables Access with Real Names

Enhanced the frame.f_locals property to return actual local variable names and values instead of generic placeholder names.

Evolution:

# Basic implementation returns:
{'local_01': value1, 'local_02': value2, ...}

# Enhanced implementation returns:
{'foo': 'hello debugger', 'bar': 123, 'my_var': [1, 2, 3], ...}

CI Testing

Failing tests in CI are :

  • 3 tests failed: fun_code fun_code_micropython native_fun_attrs_code

Open incorrect file for debugging when no breakpoints set (messaging.py)

I frequently see that the editor opens an incorrect file for debugging when no breakpoint have been set at all. This is always: micropython-lib/python-ecosys/debugpy/debugpy/common/messaging.py

DAP Monitor starting on port 5679
Will forward to 127.0.0.1:5678
Start MicroPython debugpy server first, then connect VS Code to port 5679
Listening for VS Code connection on port 5679...
VS Code connected from ('127.0.0.1', 55062)
Connected to MicroPython debugpy at 127.0.0.1:5678
DAP Monitor active - press Ctrl+C to stop

[VS Code] REQUEST: initialize (seq=1)
  Arguments: {
  "clientID": "vscode",
  "clientName": "Visual Studio Code",
  "adapterID": "debugpy",
  "pathFormat": "path",
  "linesStartAt1": true,
  "columnsStartAt1": true,
  "supportsVariableType": true,
  "supportsVariablePaging": true,
  "supportsRunInTerminalRequest": true,
  "locale": "en",
  "supportsProgressReporting": true,
  "supportsInvalidatedEvent": true,
  "supportsMemoryReferences": true,
  "supportsArgsCanBeInterpretedByShell": true,
  "supportsMemoryEvent": true,
  "supportsStartDebuggingRequest": true,
  "supportsANSIStyling": true
}

[MicroPython] RESPONSE: initialize (seq=1)
  Success: True, Request Seq: 1
  Body: {
  "supportsConditionalBreakpoints": false,
  "supportsEvaluateForHovers": true,
  "supportsSetVariable": false,
  "supportsRestartRequest": false,
  "supportTerminateDebuggee": true,
  "supportsTerminateRequest": true,
  "supportsValueFormattingOptions": false,
  "supportsConfigurationDoneRequest": true,
  "supportsGotoTargetsRequest": false,
  "supportsSetExpression": false,
  "supportsModulesRequest": false,
  "supportsClipboardContext": false,
  "supportsRestartFrame": false,
  "supportsStepInTargetsRequest": false,
  "supportsStepBack": false,
  "supportsCompletionsRequest": false,
  "supportsLoadedSourcesRequest": false,
  "supportsDisassembleRequest": false,
  "supportedChecksumAlgorithms": [],
  "supportsWriteMemoryRequest": false,
  "supportsHitConditionalBreakpoints": false,
  "supportsExceptionOptions": false,
  "supportsCancelRequest": false,
  "supportsBreakpointLocationsRequest": false,
  "additionalModuleColumns": [],
  "supportsExceptionInfoRequest": false,
  "supportSuspendDebuggee": true,
  "supportsLogPoints": false,
  "supportsDelayedStackTraceLoading": false,
  "supportsTerminateThreadsRequest": false,
  "supportsDataBreakpoints": false,
  "supportsReadMemoryRequest": false,
  "supportsFunctionBreakpoints": false
}

[MicroPython] EVENT: initialized (seq=2)

[VS Code] REQUEST: attach (seq=2)
  Arguments: {
  "name": "Attach DAP Monitor MicroPython",
  "type": "debugpy",
  "request": "attach",
  "connect": {
    "host": "localhost",
    "port": 5679
  },
  "pathMappings": [
    {
      "localRoot": "/home/jos/micropythonmicropython-lib/python-ecosystem/debugpy/test_vscode.py",
      "remoteRoot": "."
    }
  ],
  "justMyCode": false,
  "__configurationTarget": 6,
  "clientOS": "unix",
  "debugOptions": [
    "RedirectOutput",
    "ShowReturnValue"
  ],
  "showReturnValue": true,
  "workspaceFolder": "/home/jos/micropython",
  "__sessionId": "f738a358-2c01-4a7d-8bc3-9338ec810575"
}

[VS Code] REQUEST: setBreakpoints (seq=3)
  Arguments: {
  "source": {
    "name": "dap_monitor.py",
    "path": "/home/jos/micropython/micropython-lib/python-ecosys/debugpy/dap_monitor.py"
  },
  "lines": [
    64
  ],
  "breakpoints": [
    {
      "line": 64
    }
  ],
  "sourceModified": false
}

[MicroPython] RESPONSE: attach (seq=3)
  Success: True, Request Seq: 2

[MicroPython] RESPONSE: setBreakpoints (seq=4)
  Success: True, Request Seq: 3
  Body: {
  "breakpoints": [
    {
      "line": 64,
      "verified": true,
      "source": {
        "path": "/home/jos/micropython/micropython-lib/python-ecosys/debugpy/dap_monitor.py"
      }
    }
  ]
}

[VS Code] REQUEST: configurationDone (seq=4)

[MicroPython] RESPONSE: configurationDone (seq=5)
  Success: True, Request Seq: 4

[VS Code] REQUEST: threads (seq=5)

[MicroPython] RESPONSE: threads (seq=6)
  Success: True, Request Seq: 5
  Body: {
  "threads": [
    {
      "id": 1,
      "name": "main"
    }
  ]
}

[MicroPython] EVENT: stopped (seq=7)
  Body: {
  "threadId": 1,
  "reason": "breakpoint",
  "allThreadsStopped": true
}

[VS Code] REQUEST: threads (seq=6)

[MicroPython] RESPONSE: threads (seq=8)
  Success: True, Request Seq: 6
  Body: {
  "threads": [
    {
      "id": 1,
      "name": "main"
    }
  ]
}

[VS Code] REQUEST: stackTrace (seq=7)
  Arguments: {
  "threadId": 1,
  "startFrame": 0,
  "levels": 20
}

[MicroPython] RESPONSE: stackTrace (seq=9)
  Success: True, Request Seq: 7
  Body: {
  "totalFrames": 8,
  "stackFrames": [
    {
      "name": "send_message",
      "id": 0,
      "line": 53,
      "column": 1,
      "endLine": 53,
      "endColumn": 1,
      "source": {
        "path": "/home/jos/micropython/micropython-lib/python-ecosys/debugpy/debugpy/common/messaging.py"
      }
    },
    {
      "name": "send_event",
      "id": 1,
      "line": 81,
      "column": 1,
      "endLine": 81,
      "endColumn": 1,
      "source": {
        "path": "/home/jos/micropython/micropython-lib/python-ecosys/debugpy/debugpy/common/messaging.py"
      }
    },
    {
      "name": "_send_stopped_event",
      "id": 2,
      "line": 397,
      "column": 1,
      "endLine": 397,
      "endColumn": 1,
      "source": {
        "path": "/home/jos/micropython/micropython-lib/python-ecosys/debugpy/debugpy/server/debug_session.py"
      }
    },
    {
      "name": "trigger_breakpoint",
      "id": 3,
      "line": 409,
      "column": 1,
      "endLine": 409,
      "endColumn": 1,
      "source": {
        "path": "/home/jos/micropython/micropython-lib/python-ecosys/debugpy/debugpy/server/debug_session.py"
      }
    },
    {
      "name": "breakpoint",
      "id": 4,
      "line": 88,
      "column": 1,
      "endLine": 88,
      "endColumn": 1,
      "source": {
        "path": "/home/jos/micropython/micropython-lib/python-ecosys/debugpy/debugpy/public_api.py"
      }
    },
    {
      "name": "debuggable_code",
      "id": 5,
      "line": 40,
      "column": 1,
      "endLine": 40,
      "endColumn": 1,
      "source": {
        "path": "micropython-lib/python-ecosys/debugpy/test_vscode.py"
      }
    },
    {
      "name": "main",
      "id": 6,
      "line": 70,
      "column": 1,
      "endLine": 70,
      "endColumn": 1,
      "source": {
        "path": "micropython-lib/python-ecosys/debugpy/test_vscode.py"
      }
    },
    {
      "name": "<module>",
      "id": 7,
      "line": 78,
      "column": 1,
      "endLine": 78,
      "endColumn": 1,
      "source": {
        "path": "micropython-lib/python-ecosys/debugpy/test_vscode.py"
      }
    }
  ]
}

[VS Code] REQUEST: scopes (seq=8)
  Arguments: {
  "frameId": 0
}

[VS Code] REQUEST: evaluate (seq=9)
  Arguments: {
  "expression": "message",
  "frameId": 0,
  "context": "hover",
  "line": 66,
  "column": 33,
  "source": {
    "path": "vscode-remote://wsl%2Bubuntu/home/jos/micropython/micropython-lib/python-ecosys/debugpy/debugpy/common/messaging.py"
  }
}
Error sending data: [Errno 32] Broken pipe

[VS Code] REQUEST: evaluate (seq=10)
  Arguments: {
  "expression": "message",
  "frameId": 0,
  "context": "hover",
  "line": 36,
  "column": 17,
  "source": {
    "path": "vscode-remote://wsl%2Bubuntu/home/jos/micropython/micropython-lib/python-ecosys/debugpy/debugpy/common/messaging.py"
  }
}
Error sending data: [Errno 32] Broken pipe

[VS Code] REQUEST: evaluate (seq=11)
  Arguments: {
  "expression": "",
  "frameId": 0,
  "context": "hover",
  "line": 47,
  "column": 0,
  "source": {
    "path": "vscode-remote://wsl%2Bubuntu/home/jos/micropython/micropython-lib/python-ecosys/debugpy/debugpy/common/messaging.py"
  }
}
Error sending data: [Errno 32] Broken pipe

[VS Code] REQUEST: evaluate (seq=12)
  Arguments: {
  "expression": "",
  "frameId": 0,
  "context": "hover",
  "line": 47,
  "column": 0,
  "source": {
    "path": "vscode-remote://wsl%2Bubuntu/home/jos/micropython/micropython-lib/python-ecosys/debugpy/debugpy/common/messaging.py"
  }
}
Error sending data: [Errno 32] Broken pipe

[VS Code] REQUEST: evaluate (seq=13)
  Arguments: {
  "expression": "content",
  "frameId": 0,
  "context": "hover",
  "line": 47,
  "column": 9,
  "source": {
    "path": "vscode-remote://wsl%2Bubuntu/home/jos/micropython/micropython-lib/python-ecosys/debugpy/debugpy/common/messaging.py"
  }
}
Error sending data: [Errno 32] Broken pipe

Dual file opened

When I :

  • set a breakpoint in vscode_test.py
  • start debugging,
  • VSCode opens the file vscode_test.py a 2nd time in the same editor Group.
  • the existing breakpoint shows in the first file
  • both files can be used to set breakpoint

This may be related to paths used but I do not see the pattern yet. The problem resolved itself when I used the fully qualified path for the file started ${file} in the task.json file.

F5 to Debug - Autostart debug configuration

Requires setting up a launch.json file and a task.json file after that starting a debug session for the current file is pressing [F5]

  • the debugged file is the current file
  • paths are relative to the workspace folder,
  • the path to the settrace enabled micropython version is set in the task.json file ${workspaceFolder}/ports/unix/build-standard/micropython

this can be extended to other launches by adding more configurations to the launch/task.json files

  • load a module / deployment.
  • use mpremote to start the debugpy server on a remote device ( mpremote mount .)

launch.json

{
    // launch.json
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Attach DAP Monitor MicroPython",
            "type": "debugpy",
            "request": "attach",
            "connect": {
                "host": "localhost",
                "port": 5679
            },
            // "logToFile": true,
            "pathMappings": [
                {
                    // "localRoot": "${workspaceFolder}/micropython-lib/python-ecosystem/debugpy",
                    "localRoot": "${workspaceFolder}",
                    "remoteRoot": "."
                }
            ],
            "justMyCode": false,
            "preLaunchTask":  "DAP_monitor",
        },
    ]
}

Task.json

This allows to use either of the tasks to start the debugging session. Two tasks to start micropython and the DAP monitor The DAP monitor task depends on the micropython task, so it will not start before the micropython task has started.

{
    // tasks.json
    "version": "2.0.0",
    "tasks": [
        {
            "label": "DAP_monitor",
            "type": "shell",
            "command": "python micropython-lib/python-ecosys/debugpy/dap_monitor.py",
            "isBackground": true, 
            "dependsOn": [
                "micropython_debugpy_file",
            ],
            // Dummy non-empty problem matcher must be defined.
            "problemMatcher": [
                {
                    "pattern": [
                        {
                        "regexp": ".",
                        "file": 1,
                        "location": 2,
                        "message": 3,
                        }
                    ],
                    "background": {
                        "activeOnStart": true,
                        "beginsPattern": ".",
                        "endsPattern": ".",
                    }
                }
            ],
        },
        {
            "label": "micropython_debugpy_file",
            "type": "shell",
            "command": "${workspaceFolder}/ports/unix/build-standard/micropython ${file}",
            "isBackground": true,
            "presentation": {
                "focus": true,
            }, 
            // Dummy non-empty problem matcher must be defined.
            "problemMatcher": [
                {
                    "pattern": [
                        {
                        "regexp": ".",
                        "file": 1,
                        "location": 2,
                        "message": 3,
                        }
                    ],
                    "background": {
                        "activeOnStart": true,
                        "beginsPattern": ".",
                        "endsPattern": ".",
                    }
                }
            ],
        },
    ],
}

DAP Monitor

I have made a few changes to the DAP monitor to make it simpler to use. It mow auto terminates when the debug client disconnects. If it is run in a VSCode task this makes starting and stopping the debug sessions very simple.

  • Play to start
  • disconnect to stop
            # Parse and Log the message
            message = self.parse_dap(source, content)
            self.log_dap_message(source, message)
            # Check for disconnect command
            if message:
                if "disconnect" == message.get('command', message.get('event', 'unknown')):
                    print(f"\n[{source}] Disconnect command received, stopping monitor.")
                    self.disconnect = True
            return header + content

Debugging a .mpy file works - a bit

setup:

  • create somefile.py including sys.settrace setup
  • cross compile to somefile.mpy
  • start micropython in folder containing just the .mpy file
  • import somefile
  • Attach debugger
  • Breaks into debugger

this process can likely be automated better for a simpler workflow

Setting breakpoints needs to be done on 'source.py' and then it is possible to step through a .mpy. As the debugger (tries to) download the source, which fails , and the returned file contents are just an error message, the debugging experience

class Door:
def __init__(self):
self.is_open = False
self.is_locked = False
def open(self):
if self.is_locked:
print("Cannot open: Door is locked.")
else:
self.is_open = True
print("Door is now open.")
def close(self):
self.is_open = False
print("Door is now closed.")
def lock(self):
if self.is_open:
print("Cannot lock: Door is open.")
else:
self.is_locked = True
print("Door is now locked.")
def unlock(self):
self.is_locked = False
print("Door is now unlocked.")
class DoorController:
def __init__(self, door):
self.door = door
def toggle(self):
if self.door.is_open:
self.door.close()
else:
self.door.open()
def check_door_status(door):
return "open" if door.is_open else "closed"
def operate_door(controller):
controller.toggle()
return check_door_status(controller.door)
"""Start the MicroPython debug server for VS Code debugging."""
import sys
# Set sys.path to include the scratch/launcher directory.
sys.path.insert(0, '.')
sys.path.insert(1, 'micropython-lib/python-ecosys/debugpy')
import debugpy
_banner = r"""
_____ _______ ______ _______ _______ ______ ___ ___
| \| ___| __ \ | | __| __ \ | |
| -- | ___| __ < | | | | __/\ /
|_____/|_______|______/_______|_______|___| |___|
"""
def waitfor_debug():
print(_banner)
print("MicroPython VS Code Debugging Test")
print("==================================")
# Start debug server
try:
debugpy.listen()
print("Debug server attached on 127.0.0.1:5678")
print("Connecting back to VS Code debugger now...")
import scratch.launcher.target as target_main
debugpy.breakpoint()
debugpy.debug_this_thread()
# Give VS Code a moment to set breakpoints after attach
print("\nGiving VS Code time to set breakpoints...")
import time
time.sleep(2)
# Call the debuggable code function so it gets traced
result = target_main.main()
print("Target completed successfully!")
if result is None:
print("No result returned from target.my_code()")
else:
print("Result type:", type(result))
print("Result:", result)
except KeyboardInterrupt:
print("\nTest interrupted by user")
except Exception as e:
print(f"Error: {e}")
waitfor_debug()
"""Start the MicroPython debug server for VS Code debugging."""
import sys
import network
try:
sys.gettrace() # Ensure sys.settrace is available
except AttributeError:
print("sys.settrace is not available. You need a firmware compiled with debugging features.")
sys.exit(1)
try:
import debugpy
except ImportError:
print("debugpy module not found. Make sure to install")
sys.exit(1)
wlan = network.WLAN()
_banner = r"""
_____ _______ ______ _______ _______ ______ ___ ___
| \| ___| __ \ | | __| __ \ | |
| -- | ___| __ < | | | | __/\ /
|_____/|_______|______/_______|_______|___| |___|
"""
def waitfor_debug():
print(_banner)
print("MicroPython VS Code Debugging Test")
print("==================================")
# Start debug server
try:
ipv4 = wlan.ipconfig('addr4')[0]
debugpy.listen(host=ipv4, port=5678)
print("Debug server attached on 127.0.0.1:5678")
print("Connecting back to VS Code debugger now...")
import target as target_main
debugpy.breakpoint()
debugpy.debug_this_thread()
# Give VS Code a moment to set breakpoints after attach
print("\nGiving VS Code time to set breakpoints...")
import time
time.sleep(2)
# Call the debuggable code function so it gets traced
result = target_main.main()
print("Target completed successfully!")
if result is None:
print("No result returned from target.my_code()")
else:
print("Result type:", type(result))
print("Result:", result)
except KeyboardInterrupt:
print("\nTest interrupted by user")
except Exception as e:
print(f"Error: {e}")
waitfor_debug()
import sys
import other
foo = 42
bar = "Hello, MicroPython!"
def fibonacci(n):
"""Calculate fibonacci number (iterative for efficiency)."""
if n <= 1:
return n
a, b = 0, 1
for _ in range(2, n + 1):
a, b = b, a + b
return b
def local_python_variables():
dead_parrot = "Norwegian Blue"
cheese_shop = ["Cheddar", "Stilton", "Wensleydale"]
holy_grail = {"quest": "find", "knights": 12}
black_knight = {"arms": 0, "legs": 2, "status": "It's just a flesh wound!"}
spam = ["spam"] * 5
silly_walk = lambda x: f"Walked {x} meters, very silly!"
_lumberjack = {"job": "lumberjack", "location": "Canada"}
__argument_clinic = {"minutes": 5, "type": "contradiction"}
spanish_inquisition = {"expected": False, "weapons": ["fear", "surprise", "ruthless efficiency"]}
dead_bishop = "on the landing"
upper_class_twit = {"name": "Vivian Smith-Smythe-Smith", "score": 0}
ministry_of_silly_walks = True
fish_slapping_dance = ["slap", "slap", "splash"]
life_of_brian = 1979
biggus_dickus = {"friend": "Incontinentia Buttocks"}
mr_creosote = {"weight": 300, "last_meal": "wafer-thin mint"}
knights_who_say_ni = ["Ni!", "Ekke Ekke Ekke Ekke Ptang Zoo Boing!"]
holy_hand_grenade = {"count": 3, "instructions": "Three shall be the number thou shalt count"}
rabbit_of_caerbannog = {"teeth": "sharp", "danger": True}
# french_taunter = {"insults": ["Your mother was a hamster!", "I fart in your general direction!"]}
# brave_sir_robin = {"ran_away": True}
# tim_enchanter = {"magic": ["fireball", "explosion"]}
# coconuts = 2
# swallow_velocity = {"african": 11, "european": 10}
# ex_leper = {"status": "cured", "income": 0}
# confused_cat = "Meow?"
# mouse_organ = ["mouse1", "mouse2", "mouse3"]
# argument = "No it isn't!"
# cheese = "No cheese"
# penguin_on_tv = {"location": "top of the television"}
# exploding_blueprint = None
# gumby = {"name": "Mr. Gumby", "hat": True}
# spam_eggs = ["spam", "eggs"]
# norwegian_blue = {"color": "blue", "resting": True}
# camelot = {"location": "England", "song": "We're knights of the round table"}
# grail_quest = ["seek", "find", "bring back"]
# shrubbery = {"height": "medium", "delivered": True}
# blackadder = "Not Monty Python, but funny"
# argument_counter = 1
# silly = True
# parrot_owner = "Mr. Praline"
# cheese_monger = "Henry Wensleydale"
# dead_parrot_sketch = {"parrot": dead_parrot, "owner": parrot_owner}
# Return a summary dictionary for demonstration
return dead_parrot
def main():
"""The actual code we want to debug - wrapped in a function so sys.settrace will trace it."""
global foo
print("Starting debuggable code...")
# Test data - set breakpoint here (using smaller numbers to avoid slow fibonacci)
numbers = [3, 4, 5]
for i, num in enumerate(numbers):
loco = local_python_variables() # Call to generate local variables
print(f"Calculating fibonacci({num})...")
result = fibonacci(num) # <-- SET BREAKPOINT HERE (line 26)
foo += result # Modify foo to see if it gets traced
print(f"fibonacci({num}) = {result}")
print(sys.implementation)
door = other.Door()
controller = other.DoorController(door)
import pdb; pdb.set_trace()
print("Initial door status:", other.check_door_status(door))
print("Operating door...")
print("Door status after operation:", other.operate_door(controller))
print("Operating door again...")
print("Door status after operation:", other.operate_door(controller))
controller.toggle() # Should close the door
controller.toggle() # Should close the door
door.lock()
controller.toggle() # Should not open since door is locked
print(dir(door))
if __name__ == "__main__":
main()
print("Test completed successfully!")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment