From e50704a0026a84fc4adcbd3511fd69f4a2209b44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20NEDJAR?= Date: Fri, 10 Apr 2026 14:08:48 +0200 Subject: [PATCH] refactor(steami_config): Migrate calibrate_magnetometer to steami_screen UI. --- .../examples/calibrate_magnetometer.py | 202 ++++++++++-------- 1 file changed, 112 insertions(+), 90 deletions(-) diff --git a/lib/steami_config/examples/calibrate_magnetometer.py b/lib/steami_config/examples/calibrate_magnetometer.py index d2172766..02ea50f6 100644 --- a/lib/steami_config/examples/calibrate_magnetometer.py +++ b/lib/steami_config/examples/calibrate_magnetometer.py @@ -1,140 +1,162 @@ -"""Calibrate the LIS2MDL magnetometer and save to persistent config. +"""Calibrate the LIS2MDL magnetometer using the steami_screen UI. -This example runs a 3D min/max calibration by collecting samples while -the user rotates the board in all directions. The computed hard-iron -offsets and soft-iron scale factors are stored in the config zone and -survive power cycles. +This example walks through the full calibration flow: + +- press MENU to start the calibration, +- rotate the board in all directions while samples are acquired, +- compute hard-iron offsets and soft-iron scale factors, +- save the calibration to persistent SteamiConfig storage, +- reload and verify the calibration survives a fresh config load. Instructions and a countdown are displayed on the SSD1327 OLED screen. -Press MENU to start the calibration. """ import gc from time import sleep_ms +import ssd1327 from daplink_bridge import DaplinkBridge from lis2mdl import LIS2MDL from machine import I2C, SPI, Pin -from ssd1327 import WS_OLED_128X128_SPI from steami_config import SteamiConfig +from steami_screen import Screen, SSD1327Display # --- Hardware init --- i2c = I2C(1) -oled = WS_OLED_128X128_SPI( - SPI(1), - Pin("DATA_COMMAND_DISPLAY"), - Pin("RST_DISPLAY"), - Pin("CS_DISPLAY"), + +spi = SPI(1) +dc = Pin("DATA_COMMAND_DISPLAY") +res = Pin("RST_DISPLAY") +cs = Pin("CS_DISPLAY") + +display = SSD1327Display( + ssd1327.WS_OLED_128X128_SPI(spi, dc, res, cs) ) + +screen = Screen(display) + btn_menu = Pin("MENU_BUTTON", Pin.IN, Pin.PULL_UP) bridge = DaplinkBridge(i2c) config = SteamiConfig(bridge) config.load() + mag = LIS2MDL(i2c) config.apply_magnetometer_calibration(mag) -# --- Helper functions --- - - -def show(lines): - """Display centered text lines on the round OLED screen.""" - oled.fill(0) - th = len(lines) * 12 - ys = max(0, (128 - th) // 2) - for i, line in enumerate(lines): - x = max(0, (128 - len(line) * 8) // 2) - oled.text(line, x, ys + i * 12, 15) - oled.show() - - -def draw_degree(x, y, col=15): - """Draw a tiny degree symbol (3x3 circle) at pixel position.""" - oled.pixel(x + 1, y, col) - oled.pixel(x, y + 1, col) - oled.pixel(x + 2, y + 1, col) - oled.pixel(x + 1, y + 2, col) - +# --- Helpers --- def wait_menu(): - """Wait for MENU button press then release.""" while btn_menu.value() == 1: sleep_ms(10) while btn_menu.value() == 0: sleep_ms(10) -# --- Step 1: Display instructions and wait for MENU --- +def show_intro(): + screen.clear() + screen.title("COMPAS") + + screen.subtitle( + "Tournez la", + "carte dans", + "toutes les", + "directions", + ) + screen.text("Menu=demarrer", at="S") + + screen.show() + + +def show_progress(remaining): + screen.clear() + screen.title("COMPAS") + + screen.text("Acquisition...", at=(12, 44)) + screen.value(remaining, unit="s") + screen.subtitle("Tournez", "la carte") + + screen.show() + + +def show_message(*lines): + screen.clear() + screen.title("COMPAS") + screen.subtitle(*lines) + screen.show() + + +def show_results(readings): + screen.clear() + screen.title("COMPAS") + + screen.text("Resultats:", at=(24, 34)) + + y = 48 + for i, heading in enumerate(readings): + line = "{}: {} deg".format(i + 1, int(heading)) + screen.text(line, at=(16, y)) + y += 12 + + screen.text("Termine !", at=(28, 112)) + screen.show() + + +# --- Step 1: Instructions --- print("=== Magnetometer Calibration ===\n") -print("Current offsets: x={:.1f} y={:.1f} z={:.1f}".format( - mag.x_off, mag.y_off, mag.z_off)) -print("Current scales: x={:.3f} y={:.3f} z={:.3f}\n".format( - mag.x_scale, mag.y_scale, mag.z_scale)) - -show([ - "COMPAS", - "", - "Tournez la", - "carte dans", - "toutes les", - "directions", - "", - "MENU = demarrer", -]) + +show_intro() print("Press MENU to start calibration...") wait_menu() print("Starting calibration...\n") -# --- Step 2: Acquisition with countdown --- + +# --- Step 2: Acquisition --- samples = 600 delay = 20 total_sec = (samples * delay) // 1000 + xmin = ymin = zmin = 1e9 xmax = ymax = zmax = -1e9 for s in range(samples): x, y, z = mag.magnetic_field() + xmin = min(xmin, x) xmax = max(xmax, x) ymin = min(ymin, y) ymax = max(ymax, y) zmin = min(zmin, z) zmax = max(zmax, z) + if s % 50 == 0: - remain = total_sec - (s * delay) // 1000 - show([ - "COMPAS", - "", - "Acquisition...", - "", - "Continuez a", - "tourner", - "", - "{} sec".format(remain), - ]) + remaining = total_sec - (s * delay) // 1000 + show_progress(remaining) + sleep_ms(delay) + +# --- Compute calibration --- + mag.x_off = (xmax + xmin) / 2.0 mag.y_off = (ymax + ymin) / 2.0 mag.z_off = (zmax + zmin) / 2.0 + mag.x_scale = (xmax - xmin) / 2.0 or 1.0 mag.y_scale = (ymax - ymin) / 2.0 or 1.0 mag.z_scale = (zmax - zmin) / 2.0 or 1.0 print("Calibration complete!") -print(" Hard-iron offsets: x={:.1f} y={:.1f} z={:.1f}".format( - mag.x_off, mag.y_off, mag.z_off)) -print(" Soft-iron scales: x={:.3f} y={:.3f} z={:.3f}\n".format( - mag.x_scale, mag.y_scale, mag.z_scale)) -# --- Step 3: Save to config zone --- -show(["COMPAS", "", "Sauvegarde..."]) +# --- Step 3: Save --- + +show_message("Sauvegarde...") config.set_magnetometer_calibration( hard_iron_x=mag.x_off, @@ -144,42 +166,42 @@ def wait_menu(): soft_iron_y=mag.y_scale, soft_iron_z=mag.z_scale, ) + config.save() -print("Calibration saved to config zone.\n") sleep_ms(500) + # --- Step 4: Verify --- -show(["COMPAS", "", "Sauvegarde OK", "", "Verification..."]) +show_message("Sauvegarde OK", "", "Verification...") gc.collect() + config2 = SteamiConfig(bridge) config2.load() mag2 = LIS2MDL(i2c) config2.apply_magnetometer_calibration(mag2) -print("Verification (5 heading readings after reload):") -result_lines = ["COMPAS", "", "Resultats:"] +print("Verification (5 readings):") + +readings = [] + for i in range(5): heading = mag2.heading_flat_only() - line = " {}: cap={:.0f}".format(i + 1, heading) - print(" Reading {}: heading={:.1f} deg".format(i + 1, heading)) - result_lines.append(line) + readings.append(heading) + + screen.clear() + screen.title("COMPAS") + screen.value(int(heading), unit="deg", label="Mesure {}".format(i + 1)) + screen.show() + + print("Reading {}: {:.1f} deg".format(i + 1, heading)) sleep_ms(500) -result_lines.append("") -result_lines.append("Termine !") - -# Draw results with degree symbols -oled.fill(0) -th = len(result_lines) * 12 -ys = max(0, (128 - th) // 2) -for i, line in enumerate(result_lines): - x = max(0, (128 - len(line) * 8) // 2) - oled.text(line, x, ys + i * 12, 15) - if "cap=" in line: - draw_degree(x + len(line) * 8 + 1, ys + i * 12) -oled.show() - -print("\nDone! Calibration is stored and will be restored at next boot.") + +# --- Done --- + +show_results(readings) + +print("\nDone! Calibration stored.")