Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
efc5ef6
feat: Add 'steami_screen ' scope to the config and Contributing.md
Charly-sketch Apr 1, 2026
2ddb36c
feat(steami_screen): Add file structure
Charly-sketch Apr 1, 2026
7054fde
feat(steami_screen): migrate steami_screen from tutorial repo
Charly-sketch Apr 1, 2026
c0d3cc3
feat(ssd1327): Add SSD1327Display wrapper for grayscale conversion
Charly-sketch Apr 1, 2026
ba4b87e
feat(ssd1327): Remove SSD1327Display wrapper
Charly-sketch Apr 2, 2026
02f4d70
feat(steami_screen): Add SSD1327 and GC9A01 display wrappers
Charly-sketch Apr 2, 2026
14e3ce5
test(steami_screen): add basic mock tests
Charly-sketch Apr 2, 2026
a438e1b
docs(steami_screen): Add readme
Charly-sketch Apr 2, 2026
6eacc40
refactor(steami_config): calibration_magnet to use steami_screen UI
Charly-sketch Apr 2, 2026
6df2296
fix(steami_config): remove unused constants in device
Charly-sketch Apr 2, 2026
23fd027
fix(steami_screen): correct ssd1327 file name path
Charly-sketch Apr 2, 2026
7c5ec67
feat(ssd1327): add fill_rect method
Charly-sketch Apr 3, 2026
3a3033d
feat(steami_screen): add example of menu
Charly-sketch Apr 3, 2026
cb55947
feat(steami_screen): add example of bar
Charly-sketch Apr 3, 2026
abce5dd
feat(steami_screen): add example of texts
Charly-sketch Apr 3, 2026
d8f8d2f
feat(steami_screen): add example of gauge
Charly-sketch Apr 3, 2026
5502924
feat(steami_screen): add example of graph
Charly-sketch Apr 3, 2026
d2ec9ef
feat(steami_screen): add example of compass
Charly-sketch Apr 3, 2026
79d2313
feat(steami_screen): add example of smileys
Charly-sketch Apr 3, 2026
0800142
docs(steami_screen): add examples in readme
Charly-sketch Apr 3, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ Commit messages follow the [Conventional Commits](https://www.conventionalcommit

**Scopes** (optional but enforced): if provided, the scope **must** be one of the allowed values. The scope is recommended for driver-specific changes but can be omitted for cross-cutting changes.

- Driver scopes: `apds9960`, `bme280`, `bq27441`, `daplink_bridge`, `daplink_flash`, `gc9a01`, `hts221`, `im34dt05`, `ism330dl`, `lis2mdl`, `mcp23009e`, `ssd1327`, `steami_config`, `vl53l1x`, `wsen-hids`, `wsen-pads`
- Driver scopes: `apds9960`, `bme280`, `bq27441`, `daplink_bridge`, `daplink_flash`, `gc9a01`, `hts221`, `im34dt05`, `ism330dl`, `lis2mdl`, `mcp23009e`, `ssd1327`, `steami_config`, `vl53l1x`, `wsen-hids`, `wsen-pads`, `steami_screen`
- Domain scopes: `ci`, `docs`, `style`, `tests`, `tooling`

### Examples
Expand Down
1 change: 1 addition & 0 deletions commitlint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ module.exports = {
'style',
'tests',
'tooling',
'steami_screen'
],
],
'type-enum': [
Expand Down
3 changes: 3 additions & 0 deletions lib/ssd1327/ssd1327/device.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,9 @@ def scroll(self, dx, dy):
def text(self, string, x, y, col=15):
self.framebuf.text(string, x, y, col)

def fill_rect(self, x, y, w, h, col):
self.framebuf.fill_rect(x, y, w, h, col)

def write_cmd(self):
raise NotImplementedError

Expand Down
198 changes: 104 additions & 94 deletions lib/steami_config/examples/calibrate_magnetometer.py
Original file line number Diff line number Diff line change
@@ -1,140 +1,150 @@
"""Calibrate the LIS2MDL magnetometer and save to persistent config.

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.

Instructions and a countdown are displayed on the SSD1327 OLED screen.
Press MENU to start the calibration.
"""
"""Calibrate the LIS2MDL magnetometer using steami_screen UI."""

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.text("Tournez la", at=(22, 38))
screen.text("carte dans", at=(22, 50))
screen.text("toutes les", at=(22, 62))
screen.text("directions", at=(24, 74))
screen.text("Menu=demarrer", at=(10, 90))

screen.show()


def show_progress(remaining):
screen.clear()
screen.title("COMPAS")

screen.text("Acquisition...", at=(12, 44))
screen.value(remaining, at=(30, 60))
screen.text("Tournez", at=(30, 80))
screen.text("la carte", at=(28, 92))

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,
Expand All @@ -144,42 +154,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.")
Loading
Loading