cat > main.py <<'PY'
# ============================================================================
# MMC5603 text UI — Pico / MicroPython
# Safe startup, no flush(), 10s countdown before UI.
# Wiring (I2C0): GP0=SDA, GP1=SCL, 3V3, GND
# ============================================================================

from machine import I2C, Pin
import time, math, sys

BUS       = 0
FREQ_HZ   = 100_000
REF_uT    = 60.0
BAR_WIDTH = 51
UPDATE_MS = 150

def ansi(s): sys.stdout.write(s)
def clear(): ansi("\x1b[2J")
def home():  ansi("\x1b[H")
def mv(r,c): ansi("%c[%d;%dH" % (27, r, c))       # avoid f-strings
def hide():  ansi("\x1b[?25l")
def show():  ansi("\x1b[?25h")

def bar(value, span=REF_uT, width=BAR_WIDTH):
    half = (width-1)//2
    v = max(-span, min(span, value))
    idx = int(round((v/span) * half))
    cells = [' ']*width
    mid = width//2
    cells[mid] = '|'
    if idx >= 0:
        for i in range(mid+1, mid+idx+1):
            if 0 <= i < width: cells[i] = '='
    else:
        for i in range(mid-1, mid+idx-1, -1):
            if 0 <= i < width: cells[i] = '='
    return ''.join(cells)

def needle(angle_deg, width=BAR_WIDTH):
    angle_deg %= 360.0
    idx = int(round(angle_deg / 360.0 * (width-1)))
    cells = ['-']*width
    cells[width//2] = '|'
    cells[idx] = '^'
    return ''.join(cells)

class MMC5603:
    I2C_ADDR    = 0x30
    REG_OUT_X_L = 0x00
    REG_CTRL0   = 0x1B
    REG_WHOAMI  = 0x2F

    WHOAMI_EXPECTED = 0x10
    BIT_TM_M  = 0x01
    BIT_SET   = 0x08
    BIT_RESET = 0x10

    LSB_TO_uT = 0.0625 * 0.1  # 0.00625 µT/LSB

    def __init__(self, i2c): self.i2c = i2c

    def begin(self):
        scan = self.i2c.scan()
        print("I2C scan:", [hex(a) for a in scan])
        if self.I2C_ADDR not in scan:
            return False
        try:
            who = self.i2c.readfrom_mem(self.I2C_ADDR, self.REG_WHOAMI, 1)[0]
            print("WHO_AM_I=0x%02X (want ~0x10)" % who)
        except Exception as e:
            print("WHO_AM_I read failed:", e)
        return True

    def _trigger(self):
        self.i2c.writeto_mem(self.I2C_ADDR, self.REG_CTRL0, bytes([self.BIT_TM_M]))
        time.sleep_ms(10)

    def _read9(self):
        return self.i2c.readfrom_mem(self.I2C_ADDR, self.REG_OUT_X_L, 9)

    @staticmethod
    def _unpack_unsigned20(b):
        x = ((b[0] << 12) | (b[1] << 4) | (b[6] >> 4)) & 0xFFFFF
        y = ((b[2] << 12) | (b[3] << 4) | (b[6] & 0x0F)) & 0xFFFFF
        z = ((b[4] << 12) | (b[5] << 4) | (b[7] >> 4)) & 0xFFFFF
        return x, y, z

    def read_raw_diff(self):
        self.i2c.writeto_mem(self.I2C_ADDR, self.REG_CTRL0, bytes([self.BIT_SET]))
        time.sleep_ms(2)
        self._trigger()
        b_set = self._read9()
        xs, ys, zs = self._unpack_unsigned20(b_set)

        self.i2c.writeto_mem(self.I2C_ADDR, self.REG_CTRL0, bytes([self.BIT_RESET]))
        time.sleep_ms(2)
        self._trigger()
        b_rst = self._read9()
        xr, yr, zr = self._unpack_unsigned20(b_rst)

        return (xs - xr)//2, (ys - yr)//2, (zs - zr)//2

    def read_uT(self):
        x, y, z = self.read_raw_diff()
        f = self.LSB_TO_uT
        return x*f, y*f, z*f

def main():
    print("Init I2C on GP1=SCL, GP0=SDA @ %d Hz..." % FREQ_HZ)
    i2c = I2C(BUS, scl=Pin(1), sda=Pin(0), freq=FREQ_HZ)
    mmc = MMC5603(i2c)
    print("Probing MMC5603 at 0x30...")
    if not mmc.begin():
        print("Sensor not found at 0x30.")
        return

    # Friendly 10s countdown BEFORE clearing screen
    print("Starting in 10 seconds... stretch, breathe, enjoy.")
    for i in range(10, 0, -1):
        print("  %d..." % i)
        time.sleep(1)
    print("Let's go!\n")

    clear(); hide(); home()
    print("MMC5603 — text UI (Ctrl-C to quit)   scale=±%.0f µT" % REF_uT)
    try:
        while True:
            x, y, z = mmc.read_uT()
            mag = (x*x + y*y + z*z) ** 0.5
            hdg = (math.degrees(math.atan2(y, x)) + 360.0) % 360.0

            mv(2,1);  print("Heading: %6.1f°   |B|: %7.2f µT                  " % (hdg, mag))
            mv(4,1);  print("X %8.3f µT " % x + bar(x))
            mv(5,1);  print("Y %8.3f µT " % y + bar(y))
            mv(6,1);  print("Z %8.3f µT " % z + bar(z))
            mv(8,1);  print("Needle:   " + needle(hdg))
            mv(10,1); print("Keep board level; rotate slowly.                       ")
            # no sys.stdout.flush(); not needed on MicroPython
            time.sleep_ms(UPDATE_MS)
    except KeyboardInterrupt:
        pass
    finally:
        show()
        mv(12,1); print("Bye.")

if __name__ == "__main__":
    main()
PY
