feat(bq27441): Add battery pet (tamagotchi) example with OLED.#399
feat(bq27441): Add battery pet (tamagotchi) example with OLED.#399
Conversation
fefdbe5 to
5cea1ab
Compare
There was a problem hiding this comment.
Pull request overview
Adds a new bq27441 example demonstrating an OLED “battery pet” (tamagotchi-style) UI driven by fuel-gauge state-of-charge, using the board D-PAD (via MCP23009E) and buzzer for interaction.
Changes:
- New MicroPython example
tamagotchie.pyintegrating BQ27441 + SSD1327 OLED + MCP23009E D-PAD + buzzer PWM. - Implements sprite rendering, a simple “need/action” input loop, and basic success/fail sounds.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| def draw_character(cx, cy, scale, sprite): | ||
| """Draw caractere""" | ||
| fb = display.framebuf | ||
| for y, row in enumerate(sprite): | ||
| for x, color in enumerate(row): | ||
| for dy in range(scale): | ||
| for dx in range(scale): | ||
| fb.pixel(cx + x*scale + dx, cy + y*scale + dy, color) | ||
|
|
||
|
|
||
| def creat_screen(selected_index, need, sprite, charge): | ||
| """displays the screen""" |
There was a problem hiding this comment.
Several identifiers and docstrings have spelling/typo issues (creat_screen, creat_game_over_screen, soud_effect, and "Draw caractere"), which makes the example harder to follow and search for. Renaming these to correctly spelled, consistent snake_case names will improve maintainability (and also avoids introducing misspelled APIs if this code is reused).
| def soud_effect(name): | ||
| sound = { | ||
| "start": [ | ||
| (523, 120), | ||
| (659, 120), | ||
| (784, 120), | ||
| (1047, 400), | ||
| ], | ||
|
|
||
| "hungry": [ | ||
| (400, 150), | ||
| (350, 150), | ||
| (300, 300), | ||
| ], | ||
|
|
||
| "bored": [ | ||
| (500, 120), | ||
| (650, 120), | ||
| (800, 200), | ||
| ], | ||
|
|
||
| "success": [ | ||
| (600, 100), | ||
| (800, 100), | ||
| (1000, 200), | ||
| ], | ||
|
|
||
| "evolution": [ | ||
| (600, 120), | ||
| (750, 120), | ||
| (900, 120), | ||
| (1100, 150), | ||
| (1300, 300), | ||
| ], | ||
|
|
||
| "fail": [ | ||
| (500, 150), | ||
| (400, 150), | ||
| (300, 400), | ||
| ] | ||
| } | ||
| melody = sound[name] | ||
| for freq, duration_ms in melody: | ||
| buzzer_tim.freq(freq) |
There was a problem hiding this comment.
soud_effect() rebuilds the entire sound dictionary on every call, which creates a lot of allocations on MicroPython (and can increase GC pressure/fragmentation during gameplay). Move the melody table to a module-level constant and have soud_effect() only look up and play the selected melody.
| while is_alive: | ||
| selected_index = 0 | ||
| charge = fg.state_of_charge() | ||
|
|
||
| need = " " | ||
| creat_screen(selected_index, need, SPRITE_BASE, charge) | ||
| sleep_ms(1000) | ||
|
|
||
| need = random.choice(NEED) | ||
| if need == "I'm bored" : | ||
| sprite = SPRITE_SAD | ||
| soud_effect("bored") | ||
| else : | ||
| sprite = SPRITE_HUNGRY | ||
| soud_effect("hungry") | ||
|
|
||
| creat_screen(selected_index, need, sprite, charge) | ||
|
|
||
| start = ticks_ms() | ||
| win = None | ||
|
|
||
| while True: | ||
| timer = ticks_diff(ticks_ms(), start) | ||
| if timer >= 5000: | ||
| break | ||
|
|
||
|
|
||
| button = wait_for_button() | ||
| if button == "UP": | ||
| selected_index = (selected_index - 1)% len(ACTION) | ||
| creat_screen(selected_index, need, sprite, charge) | ||
| elif button == "DOWN": | ||
| selected_index = (selected_index + 1) % len(ACTION) | ||
| creat_screen(selected_index, need, sprite, charge) | ||
| elif button == "LEFT": | ||
| win = action_check(selected_index, need, win) | ||
| break | ||
| sleep_ms(20) | ||
|
|
||
| if win: | ||
| creat_screen(selected_index, need, SPRITE_HAPPY, charge) | ||
| soud_effect("success") | ||
| else: | ||
| creat_screen(selected_index, need, SPRITE_HANGRY, charge) | ||
| soud_effect("fail") | ||
| sleep_ms(1000) | ||
|
|
||
| if charge < 10: | ||
| is_alive = False | ||
| creat_game_over_screen() |
There was a problem hiding this comment.
Consider wrapping the main loop in try/finally to ensure the buzzer is silenced (pulse_width_percent(0)) on exceptions/KeyboardInterrupt. Without a cleanup path, an interrupt during a tone can leave PWM running until reset.
| while is_alive: | |
| selected_index = 0 | |
| charge = fg.state_of_charge() | |
| need = " " | |
| creat_screen(selected_index, need, SPRITE_BASE, charge) | |
| sleep_ms(1000) | |
| need = random.choice(NEED) | |
| if need == "I'm bored" : | |
| sprite = SPRITE_SAD | |
| soud_effect("bored") | |
| else : | |
| sprite = SPRITE_HUNGRY | |
| soud_effect("hungry") | |
| creat_screen(selected_index, need, sprite, charge) | |
| start = ticks_ms() | |
| win = None | |
| while True: | |
| timer = ticks_diff(ticks_ms(), start) | |
| if timer >= 5000: | |
| break | |
| button = wait_for_button() | |
| if button == "UP": | |
| selected_index = (selected_index - 1)% len(ACTION) | |
| creat_screen(selected_index, need, sprite, charge) | |
| elif button == "DOWN": | |
| selected_index = (selected_index + 1) % len(ACTION) | |
| creat_screen(selected_index, need, sprite, charge) | |
| elif button == "LEFT": | |
| win = action_check(selected_index, need, win) | |
| break | |
| sleep_ms(20) | |
| if win: | |
| creat_screen(selected_index, need, SPRITE_HAPPY, charge) | |
| soud_effect("success") | |
| else: | |
| creat_screen(selected_index, need, SPRITE_HANGRY, charge) | |
| soud_effect("fail") | |
| sleep_ms(1000) | |
| if charge < 10: | |
| is_alive = False | |
| creat_game_over_screen() | |
| try: | |
| while is_alive: | |
| selected_index = 0 | |
| charge = fg.state_of_charge() | |
| need = " " | |
| creat_screen(selected_index, need, SPRITE_BASE, charge) | |
| sleep_ms(1000) | |
| need = random.choice(NEED) | |
| if need == "I'm bored" : | |
| sprite = SPRITE_SAD | |
| soud_effect("bored") | |
| else : | |
| sprite = SPRITE_HUNGRY | |
| soud_effect("hungry") | |
| creat_screen(selected_index, need, sprite, charge) | |
| start = ticks_ms() | |
| win = None | |
| while True: | |
| timer = ticks_diff(ticks_ms(), start) | |
| if timer >= 5000: | |
| break | |
| button = wait_for_button() | |
| if button == "UP": | |
| selected_index = (selected_index - 1)% len(ACTION) | |
| creat_screen(selected_index, need, sprite, charge) | |
| elif button == "DOWN": | |
| selected_index = (selected_index + 1) % len(ACTION) | |
| creat_screen(selected_index, need, sprite, charge) | |
| elif button == "LEFT": | |
| win = action_check(selected_index, need, win) | |
| break | |
| sleep_ms(20) | |
| if win: | |
| creat_screen(selected_index, need, SPRITE_HAPPY, charge) | |
| soud_effect("success") | |
| else: | |
| creat_screen(selected_index, need, SPRITE_HANGRY, charge) | |
| soud_effect("fail") | |
| sleep_ms(1000) | |
| if charge < 10: | |
| is_alive = False | |
| creat_game_over_screen() | |
| finally: | |
| buzzer_ch.pulse_width_percent(0) |
| need = " " | ||
| creat_screen(selected_index, need, SPRITE_BASE, charge) | ||
| sleep_ms(1000) | ||
|
|
||
| need = random.choice(NEED) | ||
| if need == "I'm bored" : | ||
| sprite = SPRITE_SAD | ||
| soud_effect("bored") | ||
| else : | ||
| sprite = SPRITE_HUNGRY | ||
| soud_effect("hungry") | ||
|
|
||
| creat_screen(selected_index, need, sprite, charge) |
There was a problem hiding this comment.
The gameplay timing and “aging” behavior described in the PR don’t match the current loop: requests are not generated every 2 seconds (there’s a 1s idle + up to 5s wait + 1s result), and the charge-based child/teen/adult stages aren’t represented beyond a sprite scale change. Either update the implementation to match the described behavior, or adjust the PR description to reflect what the example actually does.
There was a problem hiding this comment.
modification made in the description of the pr
| [ 0, 0,15,15, 0, 0, 0, 0, 0,15,15, 0, 0], | ||
| [ 0,15,15,15,15, 0, 0, 0,15,15,15,15, 0], | ||
| [ 0,15,15,15,15,15,15,15,15,15,15,15, 0], | ||
| [ 0,15,15, 0, 0, 0, 0, 0, 0, 0,15,15, 0], | ||
| [ 0,15, 0, 0,15, 0, 0, 0,15, 0, 0,15, 0], | ||
| [15, 0, 0, 0, 0,15,15,15, 0, 0, 0, 0,15], | ||
| [15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,15], | ||
| [ 0,15, 0, 0, 0, 0, 0, 0, 0, 0, 0,15, 0], | ||
| [15, 0, 0,15, 0, 0, 0, 0, 0,15, 0,15, 0], | ||
| [ 0,15,15, 0, 0, 0, 0, 0, 0,15,15,15, 0], | ||
| [ 0,15, 0, 0, 0, 0, 0, 0, 0, 0, 0,15, 0], | ||
| [ 0, 0,15, 0, 0, 0, 0, 0, 0, 0,15, 0, 0], | ||
| [ 0, 0, 0,15, 0,15,15,15, 0,15, 0, 0, 0], | ||
| [ 0, 0, 0,15, 0,15, 0,15, 0,15, 0, 0, 0], | ||
| [ 0, 0, 0, 0,15, 0, 0, 0,15, 0, 0, 0, 0], |
There was a problem hiding this comment.
This file will fail ruff check due to many formatting violations in the sprite arrays (e.g., missing whitespace after commas like 0,15 and inconsistent spacing inside brackets). Running ruff format (or reformatting the sprite lists to use standard spacing) will avoid hundreds of E231/E201-style lint errors in CI.
| [ 0, 0,15,15, 0, 0, 0, 0, 0,15,15, 0, 0], | |
| [ 0,15,15,15,15, 0, 0, 0,15,15,15,15, 0], | |
| [ 0,15,15,15,15,15,15,15,15,15,15,15, 0], | |
| [ 0,15,15, 0, 0, 0, 0, 0, 0, 0,15,15, 0], | |
| [ 0,15, 0, 0,15, 0, 0, 0,15, 0, 0,15, 0], | |
| [15, 0, 0, 0, 0,15,15,15, 0, 0, 0, 0,15], | |
| [15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,15], | |
| [ 0,15, 0, 0, 0, 0, 0, 0, 0, 0, 0,15, 0], | |
| [15, 0, 0,15, 0, 0, 0, 0, 0,15, 0,15, 0], | |
| [ 0,15,15, 0, 0, 0, 0, 0, 0,15,15,15, 0], | |
| [ 0,15, 0, 0, 0, 0, 0, 0, 0, 0, 0,15, 0], | |
| [ 0, 0,15, 0, 0, 0, 0, 0, 0, 0,15, 0, 0], | |
| [ 0, 0, 0,15, 0,15,15,15, 0,15, 0, 0, 0], | |
| [ 0, 0, 0,15, 0,15, 0,15, 0,15, 0, 0, 0], | |
| [ 0, 0, 0, 0,15, 0, 0, 0,15, 0, 0, 0, 0], | |
| [0, 0, 15, 15, 0, 0, 0, 0, 0, 15, 15, 0, 0], | |
| [0, 15, 15, 15, 15, 0, 0, 0, 15, 15, 15, 15, 0], | |
| [0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 0], | |
| [0, 15, 15, 0, 0, 0, 0, 0, 0, 0, 15, 15, 0], | |
| [0, 15, 0, 0, 15, 0, 0, 0, 15, 0, 0, 15, 0], | |
| [15, 0, 0, 0, 0, 15, 15, 15, 0, 0, 0, 0, 15], | |
| [15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15], | |
| [0, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 0], | |
| [15, 0, 0, 15, 0, 0, 0, 0, 0, 15, 0, 15, 0], | |
| [0, 15, 15, 0, 0, 0, 0, 0, 0, 15, 15, 15, 0], | |
| [0, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 0], | |
| [0, 0, 15, 0, 0, 0, 0, 0, 0, 0, 15, 0, 0], | |
| [0, 0, 0, 15, 0, 15, 15, 15, 0, 15, 0, 0, 0], | |
| [0, 0, 0, 15, 0, 15, 0, 15, 0, 15, 0, 0, 0], | |
| [0, 0, 0, 0, 15, 0, 0, 0, 15, 0, 0, 0, 0], |
|
|
||
| # sound | ||
| buzzer_tim = Timer(1, freq=1000) | ||
| buzzer_ch = buzzer_tim.channel(4, Timer.PWM, pin=Pin("SPEAKER")) |
There was a problem hiding this comment.
buzzer_ch = ... uses multiple spaces before =, which is flagged by ruff (E221). Please normalize to single spaces so make lint passes.
| buzzer_ch = buzzer_tim.channel(4, Timer.PWM, pin=Pin("SPEAKER")) | |
| buzzer_ch = buzzer_tim.channel(4, Timer.PWM, pin=Pin("SPEAKER")) |
| display.text(need, 25, 20) | ||
| display.text(str(charge), 50, 10) |
There was a problem hiding this comment.
display.text() is called without an explicit color here, so it will use the FrameBuffer default (usually 1), which is nearly black on a GS4 (0–15) OLED and can make the text effectively invisible. Pass an explicit bright color (consistent with the menu text using 15).
| display.text(need, 25, 20) | |
| display.text(str(charge), 50, 10) | |
| display.text(need, 25, 20, 15) | |
| display.text(str(charge), 50, 10, 15) |
| if charge > 70 : | ||
| scale = 1 | ||
| x = 55 | ||
| y = 60 | ||
| elif charge < 70 and charge > 40 : | ||
| scale = 2 | ||
| x = 45 | ||
| y = 50 | ||
| else : |
There was a problem hiding this comment.
Battery threshold logic has both style and correctness issues: whitespace before : will fail ruff (E203), and the comparisons exclude boundary values (e.g. charge == 70 falls into the else branch). Consider using inclusive ranges (and chained comparisons) so the stage mapping matches the documented 100–70 / 70–40 / 40–10 / <10 behavior.
| if charge > 70 : | |
| scale = 1 | |
| x = 55 | |
| y = 60 | |
| elif charge < 70 and charge > 40 : | |
| scale = 2 | |
| x = 45 | |
| y = 50 | |
| else : | |
| if charge >= 70: | |
| scale = 1 | |
| x = 55 | |
| y = 60 | |
| elif 40 <= charge < 70: | |
| scale = 2 | |
| x = 45 | |
| y = 50 | |
| else: |
There was a problem hiding this comment.
Aline,
bravo pour cette première PR ! Le jeu fonctionne et c'est un très bon exemple d'intégration de plusieurs composants de la carte STeaMi (batterie, écran OLED, D-PAD, buzzer). Les sprites sont sympas et la mécanique de jeu (besoin aléatoire, fenêtre de réponse, vieillissement) est bien pensée.
Je liste quelques suggestions classées par importance. Rien de bloquant — c'est surtout des ajustements de convention et de lisibilité.
1. Typo dans le nom de fichier
tamagotchie.py → tamagotchi.py
L'orthographe anglaise (et japonaise d'origine) est "Tamagotchi" sans le e final. C'est ce nom qui sera visible pour tous les utilisateurs du projet, donc autant partir sur la bonne orthographe.
git mv lib/bq27441/examples/tamagotchie.py lib/bq27441/examples/tamagotchi.py2. Casse incohérente dans NEED
NEED = ["I'm bored", "i'm hungry"]
# ^ ^
# majuscule minusculeLes deux chaînes sont affichées sur l'écran OLED et comparées dans action_check. Harmonise la casse — en mettant une majuscule aux deux par exemple :
NEED = ["I'm bored", "I'm hungry"]Et pense à mettre à jour la comparaison dans action_check en conséquence.
3. Simplification de action_check
La fonction actuelle est un peu plus complexe qu'elle ne doit l'être :
def action_check(selected_index, need, win):
name = ACTION[selected_index]
if need == "I'm bored" and name == "play":
win = True
return win
if need == "i'm hungry" and name == "food":
win = True
return win
else:
win = False
return winLe paramètre win n'est jamais lu — il est toujours écrasé avant d'être retourné. La fonction peut simplement retourner un booléen :
def action_check(selected_index, need):
"""Check if the selected action matches the current need."""
name = ACTION[selected_index]
if need == "I'm bored" and name == "play":
return True
if need == "I'm hungry" and name == "food":
return True
return FalseC'est plus court, plus lisible, et il n'y a plus de paramètre inutilisé. L'appel dans main() devient simplement win = action_check(selected_index, need).
(Conseil général : si une variable en paramètre est toujours écrasée avant d'être lue dans la fonction, elle ne devrait pas être un paramètre — c'est une valeur de retour.)
4. Espaces avant les : (else : → else:)
À quelques endroits dans le code, il y a un espace superflu avant les deux-points :
if need == "I'm bored" : # ← espace en trop
...
else : # ← espace en tropLa convention Python (et le style du projet) est de coller le : : if ...:, else:. Ruff ne les signale pas en mode preview, mais c'est un standard PEP 8 important (E203/E275).
5. Son "evolution" défini mais jamais joué
Le dictionnaire SOUND contient une mélodie "evolution" qui n'est appelée nulle part dans le code. Deux options :
- Si elle est prévue : l'ajouter quand le tamagotchi passe d'un stade à l'autre (child → teenager → adult). Ce serait un super feedback pour le joueur.
- Si elle n'est plus nécessaire : la retirer pour garder le code propre.
6. Constantes magiques
Quelques nombres dans main() gagneraient à être nommés pour la lisibilité :
IDLE_DISPLAY_MS = 1000
RESPONSE_TIMEOUT_MS = 5000
RESULT_DISPLAY_MS = 1000Ce n'est pas obligatoire pour un fichier d'exemple, mais ça aide à comprendre la mécanique du jeu d'un coup d'œil.
7. Commentaires de section
Les séparateurs de type :
# -----------SCREEN-----------
# -----------gameplay-----------
# -----------main-----------fonctionnent, mais un style plus idiomatique en Python serait :
# --- Screen helpers ---
# --- Gameplay logic ---
# --- Main loop ---C'est d'ailleurs le style utilisé dans le Makefile du projet (# --- Linting ---, # --- Testing ---).
Récapitulatif
| Point | Priorité |
|---|---|
Renommer tamagotchie.py → tamagotchi.py |
Haute (typo visible) |
Harmoniser la casse de NEED |
Haute (bug potentiel si tu changes les comparaisons) |
Simplifier action_check (retirer le paramètre win inutile) |
Moyenne |
Retirer les espaces avant : |
Faible (style) |
Jouer ou retirer le son "evolution" |
Faible |
| Nommer les constantes magiques | Optionnel |
| Style des commentaires de section | Optionnel |
Les 3 premiers points sont ceux que je te suggère de traiter. Le reste peut venir après si tu le souhaites. N'hésite pas à poser des questions si une suggestion n'est pas claire !
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).
Address review comments from #399: 1. Rename tamagotchie.py -> tamagotchi.py (fix English spelling). 2. Harmonize NEED casing: "i'm hungry" -> "I'm hungry" and update the matching logic in action_check accordingly. 3. Simplify action_check: remove the unused `win` parameter (it was always overwritten before being read) and return a boolean directly. 4. Remove unused "evolution" sound (defined but never played). 5. Rename SPRITE_HANGRY -> SPRITE_ANGRY (standard English). 6. Fix all style issues: spaces before colons (E203), inconsistent SOUND dict indentation, missing blank lines before functions (E302), missing operator spacing in draw_character (E225), double blank lines inside blocks (E303). 7. Add docstrings to all functions and a module-level docstring. 8. Name magic timing constants: IDLE_DISPLAY_MS, RESPONSE_TIMEOUT_MS, RESULT_DISPLAY_MS. 9. Add phase comments in main() for readability (idle / need / response / result).
Address review comments from #399: 1. Rename tamagotchie.py -> tamagotchi.py (fix English spelling). 2. Harmonize NEED casing: "i'm hungry" -> "I'm hungry" and update the matching logic in action_check accordingly. 3. Simplify action_check: remove the unused `win` parameter (it was always overwritten before being read) and return a boolean directly. 4. Remove unused "evolution" sound (defined but never played). 5. Rename SPRITE_HANGRY -> SPRITE_ANGRY (standard English). 6. Fix all style issues: spaces before colons (E203), inconsistent SOUND dict indentation, missing blank lines before functions (E302), missing operator spacing in draw_character (E225), double blank lines inside blocks (E303). 7. Add docstrings to all functions and a module-level docstring. 8. Name magic timing constants: IDLE_DISPLAY_MS, RESPONSE_TIMEOUT_MS, RESULT_DISPLAY_MS. 9. Add phase comments in main() for readability (idle / need / response / result).
697c2fa to
e362948
Compare
nedseb
left a comment
There was a problem hiding this comment.
Aline, j'ai poussé un commit de corrections (e362948) qui reprend les points de ma review initiale et ceux de Copilot. Voici le détail :
1. Renommage tamagotchie.py → tamagotchi.py — l'orthographe anglaise/japonaise est "Tamagotchi" sans le e final.
2. Casse harmonisée dans NEED — "i'm hungry" → "I'm hungry" (majuscule comme "I'm bored"). La comparaison dans action_check a été mise à jour.
3. action_check simplifiée — le paramètre win (toujours écrasé, jamais lu) a été retiré. La fonction retourne directement un booléen avec une expression simple.
4. Son "evolution" retiré — il était défini mais jamais joué. S'il est prévu pour plus tard, on pourra le remettre quand le mécanisme d'évolution sera implémenté.
5. SPRITE_HANGRY → SPRITE_ANGRY — "hangry" est du slang, "angry" est le mot standard.
6. Style — espaces avant : (E203), indentation du dict SOUND, lignes vides manquantes avant les fonctions (E302), espaces manquants autour des opérateurs dans draw_character (E225), double ligne vide dans le bloc while (E303). Tout corrigé, make lint passe.
7. Constantes nommées — IDLE_DISPLAY_MS = 1000, RESPONSE_TIMEOUT_MS = 5000, RESULT_DISPLAY_MS = 1000 remplacent les nombres magiques.
8. Docstrings et commentaires — ajoutés sur toutes les fonctions et les phases du jeu dans main().
Branche rebasée sur main. Prêt à merger.
# [0.23.0](v0.22.0...v0.23.0) (2026-04-16) ### Features * **bq27441:** Add battery pet (tamagotchi) example with OLED. ([#399](#399)) ([190354f](190354f))
|
🎉 This PR is included in version 0.23.0 🎉 The release is available on:
Your semantic-release bot 📦🚀 |
Closes #336
Summary
Add an interactive Tamagotchi-style example that combines four STeaMi peripherals: the BQ27441 battery gauge, the SSD1327 128×128 OLED display, the MCP23009E D-PAD, and the PWM buzzer.
The creature ages based on the real battery percentage and periodically requests the player to feed it or play with it via the D-PAD.
Game mechanics
Every ~7 seconds, the Tamagotchi expresses a random need ("I'm bored" or "I'm hungry"). The player has a 5-second window to select the matching action (play / food) with UP/DOWN and confirm with LEFT. A correct answer triggers a happy sprite + success sound; a wrong or missed answer triggers an angry sprite + fail sound.
Drivers used
bq27441— readsstate_of_charge()to drive the aging systemssd1327— draws pixel-art sprites and text on the OLEDmcp23009e— reads the D-PAD buttons (UP / DOWN / LEFT / RIGHT)pyb.Timer— drives the PWM buzzer for sound effects (start, hungry, bored, success, fail, evolution)Test plan
make run SCRIPT=lib/bq27441/examples/tamagotchi.pyfinallyblock