Skip to content

feat(ism330dl): Add OLED spirit level example.#376

Merged
nedseb merged 4 commits intomainfrom
feat/ism330dl-spirit-level
Apr 16, 2026
Merged

feat(ism330dl): Add OLED spirit level example.#376
nedseb merged 4 commits intomainfrom
feat/ism330dl-spirit-level

Conversation

@Kaanoz-en
Copy link
Copy Markdown
Contributor

Summary

Closes #329

Adds an interactive spirit_level.py example 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

  • Created lib/ism330dl/examples/spirit_level.py.
  • Mapped X and Y acceleration data (acceleration_g()) to a 2D pixel offset.
  • Inverted the Y-axis mapping to simulate the physical behavior of an air bubble (moving towards the highest point).
  • Added a custom fill_circle() helper function to draw a smooth bubble (bypassing the lack of native ellipse support in framebuf).
  • Designed a HUD with a crosshair and dynamic background lighting that brightens when the board reaches a flat state (< 0.05g).
  • Extracted all magic numbers into clean, descriptive constants.
  • Added the new spirit_level.py example to the README.md examples table.

Checklist

  • ruff check passes
  • python -m pytest tests/ -k mock -v passes (no mock test broken)
  • Tested on hardware (if applicable)
  • README updated (if adding/changing public API)
  • Examples added/updated (if applicable)
  • Commit messages follow <scope>: <Description.> format

Copilot AI review requested due to automatic review settings April 10, 2026 12:44
@Kaanoz-en Kaanoz-en requested a review from Charly-sketch April 10, 2026 12:44
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.py implementing spirit-level visualization on the SSD1327 OLED.
  • Included a custom fill_circle() helper for drawing a filled circular bubble with framebuf.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread lib/ism330dl/examples/spirit_level.py Outdated
Comment on lines +59 to +61
# 1. Read acceleration in g-forces
ax, ay, az = imu.acceleration_g()

Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've renamed the unused Z-axis variable to _az to satisfy Ruff's F841 rule and clearly indicate it's intentionally ignored.

Comment on lines +35 to +37
for x in range(-r, r + 1):
if x*x + y*y <= r*r:
fbuf.pixel(x0 + x, y0 + y, c)
Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added the required whitespace around the operators in the fill_circle helper (x * x, etc.) to comply with Ruff E225.

Comment thread lib/ism330dl/examples/spirit_level.py Outdated
Comment on lines +68 to +72
# 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))
Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplicate comment header: "Map Acceleration to Pixel Offset" is repeated consecutively. Consider removing one copy to keep the example clean.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

@Kaanoz-en Kaanoz-en Apr 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've removed the duplicated header.

Comment on lines +1 to +5
"""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.
"""
Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@Kaanoz-en Kaanoz-en force-pushed the feat/ism330dl-spirit-level branch from 7939156 to 2566c26 Compare April 10, 2026 13:08
@Charly-sketch
Copy link
Copy Markdown
Contributor

Review

Trè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 Offset

Petit détail mais à nettoyer pour garder le code propre.


2. Position de la bulle non clampée

bubble_x = SCREEN_CENTER_X + offset_x
bubble_y = SCREEN_CENTER_Y + offset_y

Mê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ée

offset_x = int(-clamped_ay * MAX_OFFSET)

Le - est logique pour simuler une bulle physique, mais ce n’est pas expliqué.

Suggestion : ajouter un commentaire rapide pour clarifier l’intention (ex : inversion pour simuler la montée de la bulle vers le point haut).


Conclusion

Exemple 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.

@Kaanoz-en Kaanoz-en force-pushed the feat/ism330dl-spirit-level branch from 2566c26 to 66dcd0c Compare April 13, 2026 08:54
@Kaanoz-en
Copy link
Copy Markdown
Contributor Author

I've updated the PR to address all your points. Here is a quick breakdown of the changes :
Even though the duplicated comment seemed to appear on an older version, it's not there on the latest fix so It has been cleared. Moreover, great catch on the edge-case clipping. I implemented the final max()/min() clamp using the screen bounds (127) and BUBBLE_RADIUS. This guarantees the bubble will never clip out of the frame, even if the accelerometer experiences sudden spikes. I added a clear explanatory comment just above the offset calculations. It now explicitly states that the axes are swapped to match the physical display orientation, and that the negative sign is deliberately used to invert the axis, simulating how a real physical air bubble floats to the highest point.

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.
Copy link
Copy Markdown
Contributor

@nedseb nedseb left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_ay et 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 = 128 pour éliminer le 127 en dur dans le clamping

Commit fonctionnel (d6fdead) — deux corrections de justesse et de consommation :

  1. 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.

  2. imu.power_off() dans le finally — 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ès display.power_off().

Prêt à merger.

@nedseb nedseb merged commit 6c25021 into main Apr 16, 2026
9 checks passed
@nedseb nedseb deleted the feat/ism330dl-spirit-level branch April 16, 2026 19:18
semantic-release-updater Bot pushed a commit that referenced this pull request Apr 16, 2026
# [0.20.0](v0.19.0...v0.20.0) (2026-04-16)

### Features

* **ism330dl:** Add OLED spirit level example. ([#376](#376)) ([6c25021](6c25021))
@semantic-release-updater
Copy link
Copy Markdown

🎉 This PR is included in version 0.20.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

nedseb added a commit that referenced this pull request Apr 16, 2026
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.
nedseb added a commit that referenced this pull request Apr 16, 2026
* 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).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(ism330dl): Add spirit level example with OLED display.

4 participants