feat(ism330dl): Add OLED spirit level example.#376
Conversation
There was a problem hiding this comment.
Pull request overview
Adds a new interactive ISM330DL + SSD1327 OLED example that visualizes board tilt as a “bubble” moving around a center crosshair, with a brighter background when the device is near level.
Changes:
- Added
lib/ism330dl/examples/spirit_level.pyimplementing spirit-level visualization on the SSD1327 OLED. - Included a custom
fill_circle()helper for drawing a filled circular bubble withframebuf.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| # 1. Read acceleration in g-forces | ||
| ax, ay, az = imu.acceleration_g() | ||
|
|
There was a problem hiding this comment.
az is unpacked from imu.acceleration_g() but never used. With Ruff enabled for examples (no per-file ignore for F841), this should be renamed to _/_az or used to avoid an unused-variable lint failure.
There was a problem hiding this comment.
I've renamed the unused Z-axis variable to _az to satisfy Ruff's F841 rule and clearly indicate it's intentionally ignored.
| for x in range(-r, r + 1): | ||
| if x*x + y*y <= r*r: | ||
| fbuf.pixel(x0 + x, y0 + y, c) |
There was a problem hiding this comment.
The circle fill test uses operators without surrounding whitespace (x*x, r*r, etc.). Ruff explicitly enables E225, so this should be spaced (x * x, etc.) to pass linting.
There was a problem hiding this comment.
I added the required whitespace around the operators in the fill_circle helper (x * x, etc.) to comply with Ruff E225.
| # 3. Map Acceleration to Pixel Offset | ||
| # We cap the acceleration at 1.0g to avoid the bubble leaving the screen | ||
| # 3. Map Acceleration to Pixel Offset | ||
| clamped_ax = max(-1.0, min(1.0, ax)) | ||
| clamped_ay = max(-1.0, min(1.0, ay)) |
There was a problem hiding this comment.
Duplicate comment header: "Map Acceleration to Pixel Offset" is repeated consecutively. Consider removing one copy to keep the example clean.
There was a problem hiding this comment.
I've removed the duplicated header.
| """Spirit level example using ISM330DL accelerometer and SSD1327 OLED. | ||
|
|
||
| Displays a digital bubble level. The bubble moves according to the board's tilt. | ||
| When the board is perfectly flat, the bubble centers and the background lights up. | ||
| """ |
There was a problem hiding this comment.
PR description mentions adding this example to a README examples table, but lib/ism330dl/README.md currently lists only basic_read/static_orientation/motion_orientation and does not include spirit_level.py. Either update the docs or adjust the PR description so they match.
There was a problem hiding this comment.
My mistake, I missed staging the README.md file in my previous commit. It's now properly updated in the examples table and included in this push.
7939156 to
2566c26
Compare
ReviewTrès bon travail sur cet exemple. Le rendu est clair et intuitif, et l’effet “niveau à bulle” fonctionne très bien. L’utilisation de l’accéléromètre est pertinente, et le mapping vers l’écran est bien pensé. L’ajout du crosshair, de la zone centrale et du feedback visuel (changement de luminosité) rend l’exemple à la fois pédagogique et agréable à utiliser. Le code est globalement propre, lisible, avec de bonnes constantes et une structure simple à suivre. Quelques points à améliorer : 1. Duplication de commentaire# 3. Map Acceleration to Pixel Offset
# 3. Map Acceleration to Pixel OffsetPetit détail mais à nettoyer pour garder le code propre. 2. Position de la bulle non clampéebubble_x = SCREEN_CENTER_X + offset_x
bubble_y = SCREEN_CENTER_Y + offset_yMême avec le clamp à ±1g, la bulle peut sortir de l’écran ou être partiellement coupée. Suggestion : clamp final des coordonnées pour garantir qu’elle reste visible : bubble_x = max(BUBBLE_RADIUS, min(127 - BUBBLE_RADIUS, bubble_x))
bubble_y = max(BUBBLE_RADIUS, min(127 - BUBBLE_RADIUS, bubble_y))3. Inversion d’axe non expliquéeoffset_x = int(-clamped_ay * MAX_OFFSET)Le Suggestion : ajouter un commentaire rapide pour clarifier l’intention (ex : inversion pour simuler la montée de la bulle vers le point haut). ConclusionExemple très réussi et utile pour comprendre comment combiner capteur + affichage avec une interaction visuelle en temps réel. Avec ces petits ajustements, il sera encore plus propre et robuste. |
2566c26 to
66dcd0c
Compare
|
I've updated the PR to address all your points. Here is a quick breakdown of the changes : Everything is pushed and should be much more robust now. |
…level. Without calibration, the 0.05g level-detection threshold is too tight for most boards: accelerometer bias keeps the bubble off-center at rest and the "level" state may never trigger. Add a startup auto-zero phase that averages 20 samples on a flat surface and subtracts the measured bias from every subsequent reading. Also power off the IMU in the finally block so a Ctrl+C does not leave the sensor running at 104 Hz, draining the battery until the next reboot.
nedseb
left a comment
There was a problem hiding this comment.
Très bonne PR Kaan, code propre et bien structuré. J'ai poussé quelques corrections directement sur la branche :
Commits de style (28b782d, 3ea0a77) :
- Suppression d'une double ligne vide entre
clamped_ayet le commentaire (E303) - Ajout de la ligne vide manquante avant
fill_circle(E302 — ces règles sont en preview dans ruff, d'où le fait qu'elles ne se sont pas déclenchées en CI) - Extraction de
SCREEN_SIZE = 128pour éliminer le127en dur dans le clamping
Commit fonctionnel (d6fdead) — deux corrections de justesse et de consommation :
-
Auto-zero calibration au démarrage — sans calibration, le seuil de 0.05g est trop serré pour la plupart des cartes : le biais accéléromètre laisse la bulle décentrée au repos et l'état "level" peut ne jamais s'allumer. Le script moyenne maintenant 20 échantillons au démarrage (1 seconde, carte à plat) et soustrait le biais de chaque lecture.
-
imu.power_off()dans lefinally— le driver ISM330DL active accéléromètre + gyroscope dès l'init. Sans power-off à la sortie, un Ctrl+C laissait le capteur tourner à 104 Hz → consommation batterie inutile jusqu'au prochain reboot. Ajouté juste aprèsdisplay.power_off().
Prêt à merger.
# [0.20.0](v0.19.0...v0.20.0) (2026-04-16) ### Features * **ism330dl:** Add OLED spirit level example. ([#376](#376)) ([6c25021](6c25021))
|
🎉 This PR is included in version 0.20.0 🎉 The release is available on:
Your semantic-release bot 📦🚀 |
Closes #400. These three pycodestyle rules are in preview in ruff 0.11.6 and were not active because of explicit-preview-rules = true. Add them to the explicit select list and auto-fix the 11 existing violations across the codebase: E203 — whitespace before ':' (catches `else :`, `if x :`, etc.) E302 — expected 2 blank lines before function/class definition E303 — too many blank lines inside a block E203 was spotted in PR #399 (Aline's tamagotchi example) and E302/E303 in PR #376 (Kaan's spirit level) — both were caught manually because ruff did not flag them.
* tooling: Enable ruff preview rules E203, E302, and E303. Closes #400. These three pycodestyle rules are in preview in ruff 0.11.6 and were not active because of explicit-preview-rules = true. Add them to the explicit select list and auto-fix the 11 existing violations across the codebase: E203 — whitespace before ':' (catches `else :`, `if x :`, etc.) E302 — expected 2 blank lines before function/class definition E303 — too many blank lines inside a block E203 was spotted in PR #399 (Aline's tamagotchi example) and E302/E303 in PR #376 (Kaan's spirit level) — both were caught manually because ruff did not flag them. * tooling: Add steami_screen to Pylance extraPaths. * tooling: Remove deprecated and unknown settings from VSCode config. * tooling: Migrate Pylance settings from VSCode to pyproject.toml. Pylance ignores python.analysis.* in settings.json when a pyproject.toml exists and warns "cannot be set when a pyproject.toml is being used". Move typeCheckingMode, extraPaths, stubPath, and diagnosticSeverityOverrides into [tool.pyright] in pyproject.toml where Pylance reads them. This also makes the configuration IDE-agnostic (works for any pyright-based tool, not just VSCode).
Summary
Closes #329
Adds an interactive
spirit_level.pyexample demonstrating a digital bubble level using the ISM330DL accelerometer and the SSD1327 OLED display. The bubble moves dynamically based on the board's physical tilt, providing real-time visual feedback.Changes
lib/ism330dl/examples/spirit_level.py.acceleration_g()) to a 2D pixel offset.fill_circle()helper function to draw a smooth bubble (bypassing the lack of native ellipse support inframebuf).< 0.05g).spirit_level.pyexample to theREADME.mdexamples table.Checklist
ruff checkpassespython -m pytest tests/ -k mock -vpasses (no mock test broken)<scope>: <Description.>format