8x32 PI
@@ -0,0 +1,2 @@
|
||||
apercus/
|
||||
__pycache__/
|
||||
@@ -0,0 +1,265 @@
|
||||
# Coder ses animations en Python
|
||||
|
||||
Krita sert à **dessiner** des animations image par image. Le code sert à les
|
||||
**calculer** : du hasard, des mouvements continus, des formules, des
|
||||
réactions à un capteur… des choses très longues, voire impossibles, à
|
||||
dessiner à la main.
|
||||
|
||||
Ce guide s'adresse à ceux qui ont joué avec le code Arduino
|
||||
([`matrice_8x32.ino`](../matrice_8x32/matrice_8x32.ino)) et veulent continuer
|
||||
sur le Raspberry Pi. Le fichier de travail est
|
||||
[`mes_animations.py`](mes_animations.py) : **les mêmes animations que sur
|
||||
l'Arduino, traduites en Python**. Ouvrez les deux côte à côte !
|
||||
|
||||
---
|
||||
|
||||
## Sommaire
|
||||
|
||||
1. [Lancer un programme](#1-lancer-un-programme)
|
||||
2. [Python pour qui connaît l'Arduino](#2-python-pour-qui-connaît-larduino)
|
||||
3. [Les outils de dessin](#3-les-outils-de-dessin)
|
||||
4. [Exercices](#4-exercices)
|
||||
5. [Mélanger code et images](#5-mélanger-code-et-images)
|
||||
6. [Transformer son code en GIF](#6-transformer-son-code-en-gif)
|
||||
7. [Comment est construit le moteur](#7-comment-est-construit-le-moteur)
|
||||
|
||||
---
|
||||
|
||||
## 1. Lancer un programme
|
||||
|
||||
Pas de téléversement en Python : on **lance** le fichier directement.
|
||||
|
||||
| Où | Commande |
|
||||
|---|---|
|
||||
| Sur son ordinateur, avec le simulateur | `python3 mes_animations.py --simulateur` (Windows : `py` au lieu de `python3`) |
|
||||
| Sur le Pi, sur la vraie matrice | `sudo ~/vitrine-env/bin/python mes_animations.py` |
|
||||
|
||||
**Ctrl + C** (ou fermer la fenêtre du simulateur) pour arrêter.
|
||||
|
||||
> Sur le Pi, si le lecteur tourne déjà en mode vitrine, arrêtez-le d'abord :
|
||||
> `sudo systemctl stop vitrine-pixels`. Un seul programme à la fois peut
|
||||
> parler à la matrice.
|
||||
|
||||
Pour **éditer** le fichier : sur l'ordinateur, n'importe quel éditeur de
|
||||
texte (le plus confortable : **Thonny**, <https://thonny.org>, conçu pour
|
||||
apprendre Python, ou **VS Code**). Sur le Pi : `nano mes_animations.py`.
|
||||
|
||||
---
|
||||
|
||||
## 2. Python pour qui connaît l'Arduino
|
||||
|
||||
| | Arduino (C++) | Python |
|
||||
|---|---|---|
|
||||
| Fin d'instruction | `;` obligatoire | rien |
|
||||
| Bloc (dans une boucle, une fonction…) | entre `{ }` | **décalé de 4 espaces** après un `:` |
|
||||
| Commentaire | `// texte` | `# texte` |
|
||||
| Variable | `int x = 3;` | `x = 3` |
|
||||
| Fonction | `void anim() { … }` | `def anim():` + bloc décalé |
|
||||
| Boucle « pour » | `for (int x = 0; x < 32; x++) { … }` | `for x in range(32):` |
|
||||
| Compte à rebours | `for (int y = 7; y > 0; y--)` | `for y in range(7, 0, -1):` |
|
||||
| Condition | `if (x > 3) { … } else { … }` | `if x > 3:` … `else:` … |
|
||||
| « et », « ou », « non » | `&&`, `\|\|`, `!` | `and`, `or`, `not` |
|
||||
| Vrai / faux | `true` / `false` | `True` / `False` (majuscule !) |
|
||||
| Tableau | `int t[3] = {1, 2, 3};` | `t = [1, 2, 3]` (une « liste ») |
|
||||
| Couleur | `CRGB(255, 0, 0)` | `(255, 0, 0)` (un « tuple ») |
|
||||
| Couleur par teinte | `CHSV(160, 255, 255)` | `teinte(160)` |
|
||||
| Hasard | `random(32)` | `random.randrange(32)` |
|
||||
| Attendre 100 ms | `delay(100);` | `attendre(100)` |
|
||||
| Afficher | `FastLED.show();` | `montrer()` |
|
||||
| Boucle principale | `void loop() { … }` | `while True:` + bloc décalé |
|
||||
|
||||
**Le piège n° 1 de Python : l'indentation.** Les espaces en début de ligne
|
||||
ne sont pas décoratifs, ils **sont** la structure du programme :
|
||||
|
||||
```python
|
||||
for x in range(32):
|
||||
pixel(x, 0, ROUGE) # dans la boucle : exécuté 32 fois
|
||||
montrer() # dans la boucle aussi
|
||||
attendre(1000) # APRÈS la boucle : exécuté une fois
|
||||
```
|
||||
|
||||
Utilisez toujours **4 espaces** (Thonny et VS Code le font tout seuls avec
|
||||
la touche Tab). Ne mélangez jamais espaces et tabulations.
|
||||
|
||||
**Le message d'erreur est votre ami.** Python indique le fichier, la ligne
|
||||
et la nature de l'erreur, tout en bas :
|
||||
|
||||
```
|
||||
File "mes_animations.py", line 72
|
||||
attendre(20
|
||||
^
|
||||
SyntaxError: '(' was never closed
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Les outils de dessin
|
||||
|
||||
Définis dans [`matrice.py`](matrice.py), disponibles dans `mes_animations.py`
|
||||
grâce à la ligne `from matrice import *`.
|
||||
|
||||
| Outil | Effet |
|
||||
|---|---|
|
||||
| `pixel(x, y, couleur)` | colore le pixel de la colonne `x` (0 à 31) et de la ligne `y` (0 à 7). En dehors : ignoré |
|
||||
| `lire_pixel(x, y)` | renvoie la couleur actuelle d'un pixel |
|
||||
| `effacer()` | éteint toute la toile |
|
||||
| `effacer(couleur)` | remplit toute la toile d'une couleur |
|
||||
| `assombrir(25)` | assombrit toute la toile de 25 % (pour les traînées) |
|
||||
| `montrer()` | **affiche** la toile. Rien n'apparaît tant qu'on ne l'appelle pas ! |
|
||||
| `montrer(image)` | affiche une image Pillow de 32 × 8 (voir partie 5) |
|
||||
| `attendre(100)` | attend 100 millisecondes |
|
||||
| `teinte(t)` | couleur de l'arc-en-ciel : 0 rouge, 32 orange, 64 jaune, 96 vert, 128 cyan, 160 bleu, 192 violet, 224 rose |
|
||||
| `teinte(t, saturation, valeur)` | idem, avec saturation (0 = blanc) et valeur (0 = noir), de 0 à 255 |
|
||||
|
||||
**Couleurs nommées** : `NOIR`, `BLANC`, `ROUGE`, `ORANGE`, `JAUNE`, `VERT`,
|
||||
`CYAN`, `BLEU`, `VIOLET`, `MAGENTA`, `ROSE`.
|
||||
**Dimensions** : `LARGEUR` (32), `HAUTEUR` (8).
|
||||
|
||||
Le point `(0, 0)` est **en haut à gauche**, comme dans Krita.
|
||||
|
||||
---
|
||||
|
||||
## 4. Exercices
|
||||
|
||||
Pour chaque exercice, écrivez une nouvelle fonction `def anim_…():` dans
|
||||
`mes_animations.py` et ajoutez son nom dans `playlist()`. Testez dans le
|
||||
simulateur.
|
||||
|
||||
**1. Le point qui traverse.** Un pixel blanc va de gauche à droite sur la
|
||||
ligne 3, puis revient. *(Indice : deux boucles `for`, la seconde avec
|
||||
`range(31, -1, -1)`.)*
|
||||
|
||||
**2. Le balayage.** Une colonne entière (8 pixels) balaie la matrice de
|
||||
gauche à droite. *(Indice : une boucle sur `x`, et dedans une boucle sur `y`.)*
|
||||
|
||||
**3. Le dégradé fixe.** Remplissez la matrice d'un arc-en-ciel immobile, de
|
||||
`teinte(0)` à gauche à `teinte(255)` à droite. Puis faites-le défiler.
|
||||
*(Comparez avec `anim_arc_en_ciel`.)*
|
||||
|
||||
**4. La balle qui rebondit.** Un pixel avance d'un pas en `x` et en `y` à
|
||||
chaque image, et change de sens quand il touche un bord.
|
||||
|
||||
```python
|
||||
def anim_balle():
|
||||
x, y = 0, 0 # position
|
||||
dx, dy = 1, 1 # vitesse (pas par image)
|
||||
for image in range(300):
|
||||
effacer()
|
||||
pixel(x, y, ORANGE)
|
||||
montrer()
|
||||
attendre(40)
|
||||
x = x + dx
|
||||
y = y + dy
|
||||
if x <= 0 or x >= LARGEUR - 1:
|
||||
dx = -dx # rebond sur un bord gauche/droite
|
||||
if y <= 0 or y >= HAUTEUR - 1:
|
||||
dy = -dy # rebond en haut/en bas
|
||||
```
|
||||
|
||||
Variante : laissez une traînée en remplaçant `effacer()` par `assombrir(30)`.
|
||||
|
||||
**5. Le ciel étoilé.** Des pixels blancs apparaissent au hasard, très
|
||||
faiblement, puis s'allument et s'éteignent doucement. *(Indice : `assombrir`
|
||||
et `random.randrange`.)*
|
||||
|
||||
**6. Le feu.** Le bas de la matrice est orange vif, des « flammes » montent
|
||||
au hasard en s'assombrissant et en tirant vers le rouge. *(Indice : partez
|
||||
de `anim_pluie`, et faites monter au lieu de descendre.)*
|
||||
|
||||
**7. Le dessin en lettres.** Modifiez `DESSIN` dans `mes_animations.py` pour
|
||||
faire défiler votre propre motif. Créez-en un deuxième, et faites-les défiler
|
||||
l'un derrière l'autre.
|
||||
|
||||
---
|
||||
|
||||
## 5. Mélanger code et images
|
||||
|
||||
Le code peut **lire les images de l'équipe Krita** et les transformer. On
|
||||
utilise Pillow, la bibliothèque d'images :
|
||||
|
||||
```python
|
||||
from PIL import Image
|
||||
|
||||
def anim_logo_qui_defile():
|
||||
# Remplacez par le chemin d'une image de 32 × 8 exportée depuis Krita.
|
||||
logo = Image.open("animations/logo.png").convert("RGB")
|
||||
for i in range(100):
|
||||
effacer()
|
||||
# On « colle » le logo deux fois, côte à côte, décalé de i pixels :
|
||||
# ce qui sort à droite rentre à gauche (défilement en boucle).
|
||||
toile.paste(logo, (i % LARGEUR - LARGEUR, 0))
|
||||
toile.paste(logo, (i % LARGEUR, 0))
|
||||
montrer()
|
||||
attendre(60)
|
||||
```
|
||||
|
||||
Ici `toile` est l'image de 32 × 8 sur laquelle dessinent `pixel()` et
|
||||
`effacer()`. Tout ce que sait faire Pillow (coller, retourner, changer les
|
||||
couleurs, écrire du texte…) fonctionne dessus. Documentation :
|
||||
<https://pillow.readthedocs.io>.
|
||||
|
||||
---
|
||||
|
||||
## 6. Transformer son code en GIF
|
||||
|
||||
Une animation codée peut devenir un **fichier GIF**, que le lecteur jouera
|
||||
en vitrine avec les dessins de l'équipe Krita :
|
||||
|
||||
```bash
|
||||
python3 mes_animations.py --enregistrer animations/10_mes_animations.gif
|
||||
```
|
||||
|
||||
Au lieu d'afficher, le moteur **enregistre** chaque image montrée par
|
||||
`montrer()`, avec la durée de l'`attendre()` qui suit. La playlist est jouée
|
||||
**une seule fois**, puis le GIF est écrit. Vérifiez-le ensuite :
|
||||
|
||||
```bash
|
||||
python3 outils/verifier_animation.py animations/10_mes_animations.gif
|
||||
```
|
||||
|
||||
> **Attention au hasard** : un GIF enregistre **une** version de
|
||||
> l'animation. Si votre animation utilise `random`, elle sera identique à
|
||||
> chaque passage dans le GIF, alors qu'en direct elle change à chaque fois.
|
||||
|
||||
---
|
||||
|
||||
## 7. Comment est construit le moteur
|
||||
|
||||
Pour les curieux, [`matrice.py`](matrice.py) est découpé comme le programme
|
||||
Arduino, et commenté partie par partie :
|
||||
|
||||
1. **Réglages** : taille, broche GPIO, luminosité, limite de courant,
|
||||
orientation, gamma.
|
||||
2. **Couleurs** : les noms, et `teinte()` (l'équivalent de `CHSV`).
|
||||
3. **`XY()`** : la même fonction que sur l'Arduino, qui transforme une
|
||||
position (x, y) en numéro de LED dans le serpentin.
|
||||
4. **Gamma et courant** : `preparer_valeurs()` corrige les couleurs avec une
|
||||
table calculée au démarrage, applique la luminosité, estime le courant
|
||||
et assombrit l'image si elle consomme trop (comme
|
||||
`setMaxPowerInVoltsAndMilliamps` de FastLED).
|
||||
5. **Les écrans** : trois « classes » qui savent toutes `afficher()`,
|
||||
`attendre()` et `eteindre()` :
|
||||
- `MatriceReelle` : envoie les couleurs aux LED avec `rpi_ws281x` ;
|
||||
- `Simulateur` : dessine 256 ronds dans une fenêtre `tkinter` ;
|
||||
- `Enregistreur` : accumule les images dans un GIF.
|
||||
|
||||
Le reste du programme ne sait pas lequel il utilise : c'est le principe
|
||||
d'une **interface commune**. Ajouter un 4e écran (une page web, une
|
||||
deuxième matrice…) ne demanderait pas de toucher aux animations.
|
||||
6. **Outils de dessin** : `pixel()`, `effacer()`, `montrer()`…, qui
|
||||
travaillent sur la `toile`.
|
||||
|
||||
Et [`lecteur.py`](lecteur.py) n'est qu'une boucle : lister le dossier,
|
||||
charger chaque fichier en liste de couples (image, durée), et les montrer
|
||||
l'un après l'autre.
|
||||
|
||||
### Idées pour aller plus loin
|
||||
|
||||
- **Un bouton** branché sur une broche GPIO pour passer à l'animation
|
||||
suivante (bibliothèque `gpiozero`, déjà installée sur Raspberry Pi OS).
|
||||
- **Un capteur de présence** (PIR) : la matrice s'anime quand quelqu'un
|
||||
passe devant la vitrine.
|
||||
- **L'heure ou la météo** en texte défilant (la police 5 × 7 est dans
|
||||
[`outils/generer_exemples.py`](outils/generer_exemples.py)).
|
||||
- **Une page web** sur le Pi pour choisir l'animation depuis son téléphone
|
||||
(bibliothèque `flask`).
|
||||
@@ -0,0 +1,367 @@
|
||||
# Guide Krita : dessiner pour la matrice LED
|
||||
|
||||
Pour **l'équipe design**. Pas besoin de matériel ni de programmation : on
|
||||
dessine dans Krita, on exporte, on vérifie sur son ordinateur, et on dépose
|
||||
le fichier dans le dossier des animations.
|
||||
|
||||

|
||||
|
||||
> **En une phrase** : un document de **32 × 8 pixels**, un pixel = une LED,
|
||||
> des couleurs **franches**, un fond **noir**, et un export en **séquence de
|
||||
> PNG** (ou en GIF).
|
||||
|
||||
---
|
||||
|
||||
## Sommaire
|
||||
|
||||
1. [Comprendre la « toile »](#1-comprendre-la--toile-)
|
||||
2. [Créer le document](#2-créer-le-document)
|
||||
3. [Dessiner en pixel art](#3-dessiner-en-pixel-art)
|
||||
4. [Animer](#4-animer)
|
||||
5. [Ce qui change entre l'écran et les LED](#5-ce-qui-change-entre-lécran-et-les-led)
|
||||
6. [Exporter](#6-exporter)
|
||||
7. [Prévisualiser sur son ordinateur](#7-prévisualiser-sur-son-ordinateur)
|
||||
8. [Déposer l'animation](#8-déposer-lanimation)
|
||||
9. [Conseils de composition pour une vitrine](#9-conseils-de-composition-pour-une-vitrine)
|
||||
10. [Liste de contrôle avant de rendre une animation](#10-liste-de-contrôle-avant-de-rendre-une-animation)
|
||||
|
||||
---
|
||||
|
||||
## 1. Comprendre la « toile »
|
||||
|
||||
La matrice est un écran de **32 colonnes × 8 lignes** = 256 pixels. Chaque
|
||||
pixel est une LED, et les LED sont espacées de **1 cm** : la matrice mesure
|
||||
32 × 8 cm.
|
||||
|
||||
```
|
||||
x → 0 31
|
||||
y ↓ ┌────────────────────────────────┐
|
||||
0 │■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ │
|
||||
│ │ 8 lignes
|
||||
7 │■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ │
|
||||
└────────────────────────────────┘
|
||||
32 colonnes
|
||||
```
|
||||
|
||||
Quelques ordres de grandeur pour se repérer :
|
||||
|
||||
- un caractère lisible fait **5 pixels de large × 7 de haut** : on écrit
|
||||
**5 lettres** à la fois, pas plus. Un texte plus long doit **défiler** ;
|
||||
- un visage simple tient dans **8 × 8** ;
|
||||
- une ligne de 1 pixel d'épaisseur se voit très bien : c'est une rangée de LED.
|
||||
|
||||
> **La matrice finale fera 39 × 7.** Si vous préparez déjà des animations
|
||||
> pour elle, créez des documents de **39 × 7** : sur la matrice d'essai, le
|
||||
> lecteur les centrera (et coupera ce qui dépasse sur les côtés).
|
||||
|
||||
---
|
||||
|
||||
## 2. Créer le document
|
||||
|
||||
1. **Fichier → Nouveau** (*File → New*) → **Document personnalisé**.
|
||||
2. Réglages :
|
||||
|
||||
| Réglage | Valeur |
|
||||
|---|---|
|
||||
| Largeur | **32** pixels |
|
||||
| Hauteur | **8** pixels |
|
||||
| Résolution | peu importe (72 ppp) |
|
||||
| Modèle de couleur | **RVB/Alpha**, **8 bits** entier, profil **sRGB** (celui par défaut) |
|
||||
| Contenu / arrière-plan | **noir** (ou transparent : le transparent devient noir, donc éteint) |
|
||||
|
||||
3. **Zoomez** énormément (touche `+`, ou la molette avec `Ctrl`) : à 3200 %,
|
||||
chaque pixel fait environ 3 cm à l'écran.
|
||||
4. **Grille de pixels** : Krita dessine une fine grille quand on zoome
|
||||
beaucoup. Si elle n'apparaît pas : **Configuration → Configurer Krita →
|
||||
Affichage → Grille de pixels**, et baissez le seuil d'affichage.
|
||||
|
||||
> ⚠️ **Ne dessinez pas en grand pour réduire ensuite.** Une image de 1920 ×
|
||||
> 480 réduite à 32 × 8 devient une bouillie floue. Dessinez **directement à
|
||||
> la taille réelle** : c'est le principe du pixel art.
|
||||
|
||||
### Importer la palette « Vitrine pixels »
|
||||
|
||||
Le fichier [`krita/palette_vitrine_pixels.gpl`](krita/palette_vitrine_pixels.gpl)
|
||||
contient 37 couleurs choisies pour bien rendre sur les LED (couleurs pures,
|
||||
moyennes, sombres et pastel, plus noir, gris et blanc).
|
||||
|
||||
1. **Configuration → Gérer les ressources…** → **Importer des ressources**.
|
||||
2. Type de ressource : **Palettes** ; choisissez le fichier `.gpl`.
|
||||
3. Dans le panneau **Palette** (*Configuration → Panneaux → Palette*),
|
||||
sélectionnez « Vitrine pixels (LED) ».
|
||||
|
||||
---
|
||||
|
||||
## 3. Dessiner en pixel art
|
||||
|
||||
La règle d'or : **pas de flou, pas de pixels « intermédiaires »**. Chaque
|
||||
pixel a une couleur franche, choisie exprès.
|
||||
|
||||
| Outil | Réglage |
|
||||
|---|---|
|
||||
| **Brosse** | un préréglage **pixel art** (tapez « pixel » dans la recherche du panneau *Préréglages de brosses*), taille **1 px** |
|
||||
| **Gomme** | même brosse, touche `E` pour basculer en mode gomme |
|
||||
| **Remplissage** (pot de peinture) | décocher **Anticrénelage** (*Anti-aliasing*) dans les options de l'outil |
|
||||
| **Sélections** | décocher **Anticrénelage** dans les options de l'outil |
|
||||
| **Transformation** (déplacer, tourner, agrandir) | Filtre : **Plus proche voisin** (*Nearest neighbour*) ; sinon les bords deviennent flous |
|
||||
| **Image → Mettre à l'échelle** | Filtre : **Boîte / Plus proche voisin** (*Box / Nearest neighbour*) |
|
||||
|
||||
**Comment savoir s'il y a du flou ?** L'outil
|
||||
[`verifier_animation.py`](#7-prévisualiser-sur-son-ordinateur) compte les
|
||||
couleurs différentes : au-delà de 64, il y a sans doute de l'anticrénelage
|
||||
ou un dégradé non voulu.
|
||||
|
||||
---
|
||||
|
||||
## 4. Animer
|
||||
|
||||
1. **Fenêtre → Espace de travail → Animation** : Krita affiche le
|
||||
**montage** (*timeline*) et les **pelures d'oignon** (*onion skin*, qui
|
||||
montrent en transparence l'image d'avant et d'après).
|
||||
2. **Images par seconde** : réglez-les dans le panneau *Animation* (ou
|
||||
*Montage*) **dès le début**. Conseils :
|
||||
- **12 i/s** : le rythme classique du dessin animé, idéal en pixel art ;
|
||||
- **24 i/s** : pour des mouvements fluides (deux fois plus d'images à dessiner) ;
|
||||
- un **texte qui défile** d'1 pixel par image : 12 à 20 i/s.
|
||||
3. Pour créer une image : clic droit sur une case du montage → **Créer une
|
||||
image vide** ou **Dupliquer l'image**.
|
||||
4. **Durée** : réglez la plage de lecture (début / fin) pour qu'elle
|
||||
corresponde exactement à votre animation. Une animation **en boucle**
|
||||
doit avoir une dernière image qui « enchaîne » bien avec la première.
|
||||
|
||||
> **Animation courte ?** Pas de souci : le lecteur rejoue chaque animation
|
||||
> jusqu'à ce qu'elle ait duré au moins **10 secondes**. Un cœur qui bat en
|
||||
> 1 seconde battra 10 fois.
|
||||
|
||||
---
|
||||
|
||||
## 5. Ce qui change entre l'écran et les LED
|
||||
|
||||
L'écran de votre ordinateur et la matrice ne produisent pas la lumière de la
|
||||
même façon. Le programme compense au mieux (c'est la **correction gamma**),
|
||||
mais certaines choses ne passent pas :
|
||||
|
||||
### Les couleurs très sombres disparaissent
|
||||
|
||||
En intérieur, la matrice tourne à environ **15 % de sa puissance** (sinon elle
|
||||
éblouit). Chaque LED n'a que 256 niveaux de lumière ; à 15 %, il n'en reste
|
||||
qu'une quarantaine, et les plus sombres valent **zéro**.
|
||||
|
||||
| Couleur dans Krita (valeur, V de TSV) | Sur la matrice |
|
||||
|---|---|
|
||||
| 100 % à 60 % | ✅ bien visible |
|
||||
| 60 % à 35 % | ✅ visible, sombre |
|
||||
| 35 % à 20 % | ⚠️ très faible, les nuances se mélangent |
|
||||
| moins de 20 % | ❌ **éteint** |
|
||||
|
||||
**Conséquence** : les dégradés subtils, les ombres légères, les fonds
|
||||
« presque noirs » ne se voient pas. Travaillez par **aplats** francs, comme
|
||||
une affiche sérigraphiée.
|
||||
|
||||
### Les couleurs saturées rendent mieux
|
||||
|
||||
- Les LED excellent dans les **couleurs pures** : rouge, vert, bleu, et leurs
|
||||
mélanges vifs (jaune, cyan, magenta, orange, violet).
|
||||
- Les **marrons**, les **beiges**, les **gris colorés** sont décevants : ils
|
||||
ressemblent à de l'orange terne ou à du blanc sale.
|
||||
- Le **blanc** tire souvent un peu vers le bleu. Un « blanc chaud »
|
||||
(255, 200, 140) est plus agréable.
|
||||
|
||||
### Le noir n'est pas du noir, c'est de l'éteint
|
||||
|
||||
Sur la matrice, le noir = LED éteinte = on voit la surface du panneau (ou ce
|
||||
qu'il y a derrière la vitrine). **Le fond noir est la norme.** Un grand fond
|
||||
coloré est possible, mais il éblouit et consomme beaucoup.
|
||||
|
||||
### La consommation électrique
|
||||
|
||||
Plus il y a de pixels allumés et clairs, plus la matrice consomme. Une image
|
||||
**entièrement blanche** dépasse ce que l'alimentation peut fournir : le
|
||||
programme baisse alors automatiquement la luminosité de cette image. Ce n'est
|
||||
pas dangereux, mais l'image paraîtra plus terne que les autres.
|
||||
`verifier_animation.py` vous le signale.
|
||||
|
||||
---
|
||||
|
||||
## 6. Exporter
|
||||
|
||||
**Fichier → Rendu de l'animation…** (*File → Render Animation*).
|
||||
|
||||
### Méthode conseillée : séquence de PNG
|
||||
|
||||
C'est la plus fiable : pas de perte de couleurs, pas de logiciel
|
||||
supplémentaire.
|
||||
|
||||
1. Choisissez **Séquence d'images** (*Image Sequence*), format **PNG**.
|
||||
2. **Dossier** : créez un dossier **au nom de l'animation**, par exemple
|
||||
`05_feu_de_camp`.
|
||||
3. **Nom de base** : `feu` → Krita crée `feu_0000.png`, `feu_0001.png`…
|
||||
4. Taille : laissez **32 × 8**.
|
||||
5. **Rendu**.
|
||||
6. Dans ce dossier, créez un fichier texte **`images_par_seconde.txt`** qui
|
||||
contient seulement le nombre d'images par seconde de votre animation,
|
||||
par exemple `12`. (Sans ce fichier, le lecteur joue à 12 i/s.)
|
||||
|
||||
Résultat :
|
||||
|
||||
```
|
||||
05_feu_de_camp/
|
||||
├── feu_0000.png
|
||||
├── feu_0001.png
|
||||
├── …
|
||||
└── images_par_seconde.txt ← contient « 12 »
|
||||
```
|
||||
|
||||
### Autre méthode : GIF animé
|
||||
|
||||
Un seul fichier, pratique à partager, et la vitesse est enregistrée dedans.
|
||||
|
||||
1. Choisissez **Vidéo** et le format **GIF**.
|
||||
2. Krita a besoin du logiciel gratuit **FFmpeg** pour ça. S'il le demande :
|
||||
téléchargez-le (<https://ffmpeg.org/download.html>) et indiquez son
|
||||
emplacement dans la fenêtre de rendu.
|
||||
3. Taille **32 × 8**. Nommez le fichier, par exemple `05_feu_de_camp.gif`.
|
||||
|
||||
> **Limites du GIF** : 256 couleurs maximum (largement assez ici), et une
|
||||
> durée d'image arrondie au centième de seconde. Le **PNG animé (APNG)** et le
|
||||
> **WebP animé** fonctionnent aussi, si vous préférez.
|
||||
|
||||
### Ce qui ne marche pas
|
||||
|
||||
- Les fichiers **`.kra`** (le format de travail de Krita) : exportez toujours.
|
||||
- Les **vidéos MP4, WebM, MKV** : ce sont des formats compressés qui abîment
|
||||
les pixels. Utilisez PNG ou GIF.
|
||||
|
||||
### Une image fixe
|
||||
|
||||
Un logo, une affiche : **Fichier → Exporter…** en **PNG**. Le lecteur
|
||||
l'affiche 10 secondes.
|
||||
|
||||
---
|
||||
|
||||
## 7. Prévisualiser sur son ordinateur
|
||||
|
||||
Deux outils, qui fonctionnent sans la matrice. Il faut **Python** et la
|
||||
bibliothèque **Pillow** (une seule fois) :
|
||||
|
||||
| Système | Installer Python | Puis installer Pillow |
|
||||
|---|---|---|
|
||||
| **Windows** | <https://www.python.org/downloads/> (cochez **« Add python.exe to PATH »**) | `py -m pip install pillow` |
|
||||
| **macOS** | <https://www.python.org/downloads/> | `python3 -m pip install pillow` |
|
||||
| **Linux** | déjà installé | `sudo apt install python3-pil python3-tk` |
|
||||
|
||||
Ensuite, ouvrez un terminal **dans le dossier `8x32 PI`** (sous Windows,
|
||||
remplacez `python3` par `py` dans les commandes ci-dessous).
|
||||
|
||||
### Le vérificateur
|
||||
|
||||
```bash
|
||||
python3 outils/verifier_animation.py chemin/vers/05_feu_de_camp
|
||||
```
|
||||
|
||||
Il affiche un rapport :
|
||||
|
||||
```
|
||||
05_feu_de_camp
|
||||
Type : séquence de 24 fichiers (12 images/s)
|
||||
Taille : 32 × 8 ✓
|
||||
Images : 24, durée 2.00 s (≈ 12.0 images/s)
|
||||
Couleurs : 9 différentes ✓
|
||||
Trop sombre : aucune ✓
|
||||
Courant : moyen 0.61 A, maximum 0.84 A ✓
|
||||
Aperçu : apercus/05_feu_de_camp_apercu.png et apercus/05_feu_de_camp_apercu.gif
|
||||
```
|
||||
|
||||
Et il fabrique dans le dossier `apercus` une **planche** (toutes les images
|
||||
l'une sous l'autre) et un **GIF agrandi « façon LED »**, à partager avec
|
||||
l'équipe.
|
||||
|
||||
| Message | Signification |
|
||||
|---|---|
|
||||
| `⚠ attendu 32 × 8` | mauvaise taille de document : l'image sera centrée et coupée |
|
||||
| `⚠ beaucoup ! anticrénelage ?` | plus de 64 couleurs : du flou s'est glissé dans le dessin |
|
||||
| `Trop sombre : N couleur(s) seront ÉTEINTES` | des couleurs trop foncées (partie 5) |
|
||||
| `⚠ limité par le logiciel` | image trop claire et trop remplie : elle sera assombrie |
|
||||
|
||||
### Le simulateur
|
||||
|
||||
```bash
|
||||
python3 lecteur.py --simulateur chemin/vers/05_feu_de_camp
|
||||
```
|
||||
|
||||
Une fenêtre s'ouvre et joue l'animation **en boucle, à la bonne vitesse**,
|
||||
avec 256 ronds qui imitent les LED. Sans chemin, il joue tout le dossier
|
||||
`animations`. Fermez la fenêtre pour arrêter.
|
||||
|
||||
> Le simulateur montre les couleurs **telles que dans Krita**. Il ne montre
|
||||
> pas la disparition des couleurs très sombres : pour ça, le vérificateur ou,
|
||||
> mieux, **la vraie matrice**. Testez sur la matrice le plus tôt possible.
|
||||
|
||||
---
|
||||
|
||||
## 8. Déposer l'animation
|
||||
|
||||
Le lecteur joue **tout ce qui se trouve dans le dossier des animations**, par
|
||||
**ordre alphabétique**. Pour choisir l'ordre, commencez les noms par un
|
||||
numéro : `01_`, `02_`, `03_`…
|
||||
|
||||
**Noms de fichiers** : sans accent, sans espace, sans caractère spécial.
|
||||
✅ `07_pluie_de_coeurs.gif` ❌ `Pluie de cœurs (v2 finale!!).gif`
|
||||
|
||||
Selon l'organisation de l'équipe :
|
||||
|
||||
- **Carte SD** : on éteint le Pi, on sort la carte, on la met dans
|
||||
l'ordinateur, on copie les animations dans le dossier `animations` du
|
||||
lecteur nommé **`bootfs`**, on remet la carte. Voir
|
||||
[MODE_VITRINE.md](MODE_VITRINE.md#3-changer-les-animations).
|
||||
- **Par le réseau** : voir [MODE_VITRINE.md](MODE_VITRINE.md#3-changer-les-animations).
|
||||
|
||||
Pour **retirer** une animation de la boucle sans la supprimer : déplacez-la
|
||||
dans un sous-dossier dont le nom commence par un point, par exemple
|
||||
`.archives` (les noms en « . » sont ignorés).
|
||||
|
||||
---
|
||||
|
||||
## 9. Conseils de composition pour une vitrine
|
||||
|
||||
- **Lisibilité depuis la rue** : à 3 mètres, un pixel isolé est un point
|
||||
lumineux. Les formes de 3 pixels et plus se lisent bien. Un contraste fort
|
||||
(couleur vive sur noir) se lit de loin.
|
||||
- **Le mouvement attire l'œil** : une animation lente et régulière se regarde
|
||||
longtemps ; une image fixe passe inaperçue.
|
||||
- **Texte** : 5 lettres visibles à la fois. Un défilement d'**1 pixel toutes
|
||||
les 50 à 80 ms** se lit confortablement. Plus vite, on ne lit plus.
|
||||
- **Boucles** : soignez la jonction fin → début. Un saut se remarque
|
||||
beaucoup plus sur 256 pixels que sur un écran.
|
||||
- **Diffuseur** : si la matrice est placée derrière un matériau diffusant
|
||||
(calque, plexiglas dépoli), les pixels se fondent et les détails d'un pixel
|
||||
disparaissent. Testez avec le diffuseur réel.
|
||||
- **Variété** : alternez animations chargées et calmes, couleurs chaudes et
|
||||
froides. L'ordre des numéros, c'est votre montage.
|
||||
|
||||
### ⚠️ Sécurité : pas de flashs rapides
|
||||
|
||||
Une vitrine est vue par **tout le monde**, y compris des personnes
|
||||
**épileptiques photosensibles**. Les règles d'accessibilité (WCAG) demandent :
|
||||
|
||||
- **pas plus de 3 flashs par seconde** (un flash = passage brusque du sombre
|
||||
au clair puis retour) ;
|
||||
- surtout **pas de grands aplats rouges** qui clignotent ;
|
||||
- pas de clignotement de toute la matrice à la fois.
|
||||
|
||||
Un stroboscope est très efficace pour attirer l'œil… et peut provoquer une
|
||||
crise. On s'en passe.
|
||||
|
||||
---
|
||||
|
||||
## 10. Liste de contrôle avant de rendre une animation
|
||||
|
||||
- [ ] Document en **32 × 8** pixels (ou 39 × 7 pour la matrice finale)
|
||||
- [ ] Pas de flou : **moins de 64 couleurs** (vérificateur)
|
||||
- [ ] Pas de couleurs **trop sombres** (vérificateur)
|
||||
- [ ] Fond **noir** ou transparent
|
||||
- [ ] Vitesse choisie : `images_par_seconde.txt` présent (séquence) ou GIF
|
||||
- [ ] La boucle s'enchaîne bien (simulateur)
|
||||
- [ ] **Pas plus de 3 flashs par seconde**
|
||||
- [ ] Nom de fichier : `NN_nom_sans_accent`
|
||||
- [ ] Testé sur la vraie matrice au moins une fois
|
||||
@@ -0,0 +1,264 @@
|
||||
# Préparer le Raspberry Pi, pas à pas
|
||||
|
||||
Ce guide part d'une carte SD vide et va jusqu'à la matrice qui s'allume.
|
||||
Comptez **une heure** la première fois, dont beaucoup d'attente.
|
||||
|
||||
> **Système plus ancien ?** Le projet fonctionne aussi sur **Raspbian 11
|
||||
> « Bullseye »** (cartes préparées avant fin 2023, testé sur un Pi 3 Model B).
|
||||
> Seule différence : la partie de la carte SD lisible par tous les
|
||||
> ordinateurs s'appelle **`/boot`** au lieu de **`/boot/firmware`**. Partout
|
||||
> où ce guide écrit `/boot/firmware`, lisez `/boot`. Le script d'installation
|
||||
> et le lecteur s'en rendent compte tout seuls.
|
||||
|
||||
On utilise le Pi **« sans écran »** (on dit *headless*) : pas de clavier, pas
|
||||
d'écran, pas de souris. On lui parle **depuis son ordinateur portable**, par
|
||||
le Wi-Fi. C'est comme ça qu'il vivra dans la vitrine.
|
||||
|
||||
---
|
||||
|
||||
## Sommaire
|
||||
|
||||
1. [Ce qu'il faut](#1-ce-quil-faut)
|
||||
2. [Installer le système sur la carte SD](#2-installer-le-système-sur-la-carte-sd)
|
||||
3. [Premier démarrage](#3-premier-démarrage)
|
||||
4. [Se connecter au Pi (SSH)](#4-se-connecter-au-pi-ssh)
|
||||
5. [Copier le projet sur le Pi](#5-copier-le-projet-sur-le-pi)
|
||||
6. [Installation automatique](#6-installation-automatique)
|
||||
7. [Ce que fait l'installation (pour comprendre)](#7-ce-que-fait-linstallation-pour-comprendre)
|
||||
8. [Tester](#8-tester)
|
||||
9. [Les commandes de survie du terminal](#9-les-commandes-de-survie-du-terminal)
|
||||
10. [Problèmes fréquents](#10-problèmes-fréquents)
|
||||
|
||||
---
|
||||
|
||||
## 1. Ce qu'il faut
|
||||
|
||||
- Un Raspberry Pi **3B+, 4 ou Zero 2 W** et son bloc secteur.
|
||||
- Une carte micro-SD de 16 Go ou plus, et un lecteur de carte pour l'ordinateur.
|
||||
- Un ordinateur (Windows, macOS ou Linux) **sur le même réseau Wi-Fi** que le Pi.
|
||||
- Le nom et le mot de passe de ce réseau Wi-Fi.
|
||||
|
||||
> **Réseau de l'école** : les réseaux avec identifiant personnel (type
|
||||
> *eduroam*) sont compliqués à configurer sur un Pi. Le plus simple : utiliser
|
||||
> le **partage de connexion d'un téléphone** (point d'accès), auquel on
|
||||
> connecte le Pi **et** l'ordinateur. Notez son nom et son mot de passe.
|
||||
> Attention : le Pi 3B+ et le Zero 2 W ne voient que le Wi-Fi **2,4 GHz**
|
||||
> (sur iPhone : activer « Maximiser la compatibilité »).
|
||||
|
||||
---
|
||||
|
||||
## 2. Installer le système sur la carte SD
|
||||
|
||||
On utilise le logiciel officiel **Raspberry Pi Imager**.
|
||||
|
||||
1. Téléchargez-le et installez-le : <https://www.raspberrypi.com/software/>
|
||||
2. Insérez la carte SD dans l'ordinateur et lancez Raspberry Pi Imager.
|
||||
3. **Modèle** : choisissez votre Raspberry Pi.
|
||||
4. **Système d'exploitation** : *Raspberry Pi OS (other)* →
|
||||
**Raspberry Pi OS Lite (64-bit)**.
|
||||
« Lite » = sans bureau graphique : plus léger, démarre plus vite, et on
|
||||
n'a pas d'écran de toute façon.
|
||||
5. **Stockage** : votre carte SD. ⚠️ **Tout son contenu sera effacé.**
|
||||
6. Quand Imager propose de **personnaliser** le système (*customisation*,
|
||||
*Modifier réglages*), dites **oui** et remplissez :
|
||||
|
||||
| Réglage | Valeur conseillée | Pourquoi |
|
||||
|---|---|---|
|
||||
| Nom d'hôte (*hostname*) | `vitrine` | le nom du Pi sur le réseau : on le trouvera à l'adresse `vitrine.local` |
|
||||
| Nom d'utilisateur | `pixel` (par exemple) | votre compte sur le Pi |
|
||||
| Mot de passe | à choisir, **à noter** | |
|
||||
| Wi-Fi : nom (SSID) et mot de passe | ceux du réseau ou du téléphone | pour que le Pi se connecte tout seul |
|
||||
| Pays du Wi-Fi | `FR` | obligatoire pour activer le Wi-Fi |
|
||||
| Fuseau horaire / clavier | `Europe/Paris` / `fr` | pour que l'heure soit juste |
|
||||
| **SSH** (onglet *Services*) | **activer**, avec mot de passe | pour se connecter depuis l'ordinateur |
|
||||
|
||||
7. Lancez l'écriture et patientez (5 à 15 minutes). Imager vérifie la carte
|
||||
à la fin, puis l'éjecte.
|
||||
|
||||
> **Si vous avez plusieurs Pi**, donnez-leur des noms différents
|
||||
> (`vitrine1`, `vitrine2`…), sinon ils se gêneront sur le réseau.
|
||||
|
||||
---
|
||||
|
||||
## 3. Premier démarrage
|
||||
|
||||
1. Insérez la carte SD dans le Pi (contacts vers la carte).
|
||||
2. Branchez le bloc secteur. La LED rouge s'allume, la verte clignote :
|
||||
le Pi travaille.
|
||||
3. **Attendez 3 à 5 minutes** : au premier démarrage, le Pi s'installe,
|
||||
redémarre une ou deux fois, puis se connecte au Wi-Fi.
|
||||
|
||||
Pas besoin de brancher la matrice pour l'instant.
|
||||
|
||||
---
|
||||
|
||||
## 4. Se connecter au Pi (SSH)
|
||||
|
||||
**SSH** permet de taper des commandes sur le Pi depuis son ordinateur, comme
|
||||
si on avait un clavier branché dessus.
|
||||
|
||||
Ouvrez un **terminal** :
|
||||
|
||||
- **Windows** : menu Démarrer → tapez `PowerShell` → Entrée ;
|
||||
- **macOS** : Applications → Utilitaires → **Terminal** ;
|
||||
- **Linux** : le terminal habituel.
|
||||
|
||||
Tapez (en remplaçant `pixel` par votre nom d'utilisateur) :
|
||||
|
||||
```bash
|
||||
ssh pixel@vitrine.local
|
||||
```
|
||||
|
||||
- La première fois, il demande si vous faites confiance à cet ordinateur :
|
||||
tapez `yes` puis Entrée.
|
||||
- Puis votre **mot de passe**. ⚠️ **Rien ne s'affiche quand on le tape**, pas
|
||||
même des étoiles : c'est normal. Tapez-le et faites Entrée.
|
||||
|
||||
Vous êtes connecté quand la ligne commence par `pixel@vitrine:~ $`.
|
||||
Tout ce que vous tapez maintenant s'exécute **sur le Pi**.
|
||||
|
||||
Pour se déconnecter : `exit`.
|
||||
|
||||
---
|
||||
|
||||
## 5. Copier le projet sur le Pi
|
||||
|
||||
Ouvrez un **deuxième terminal sur votre ordinateur** (pas dans la session
|
||||
SSH), placez-vous dans le dossier qui contient `8x32 PI`, puis :
|
||||
|
||||
```bash
|
||||
scp -r "8x32 PI" pixel@vitrine.local:~/vitrine
|
||||
```
|
||||
|
||||
`scp` = copie par SSH. Le dossier s'appellera simplement `vitrine` sur le Pi
|
||||
(sans espace dans le nom, c'est plus pratique dans un terminal).
|
||||
|
||||
> **Plutôt à la souris ?** Les logiciels gratuits **WinSCP** (Windows),
|
||||
> **Cyberduck** (macOS) ou **FileZilla** (tous) permettent de glisser-déposer
|
||||
> des fichiers sur le Pi. Choisissez le protocole **SFTP**, serveur
|
||||
> `vitrine.local`, votre nom d'utilisateur et votre mot de passe.
|
||||
|
||||
---
|
||||
|
||||
## 6. Installation automatique
|
||||
|
||||
Dans la **session SSH** (sur le Pi) :
|
||||
|
||||
```bash
|
||||
cd ~/vitrine
|
||||
```
|
||||
|
||||
```bash
|
||||
bash installation/installer.sh
|
||||
```
|
||||
|
||||
Le script installe tout ce qu'il faut (5 à 15 minutes). Il demande parfois
|
||||
votre mot de passe (pour `sudo`). À la fin, **redémarrez le Pi** :
|
||||
|
||||
```bash
|
||||
sudo reboot
|
||||
```
|
||||
|
||||
La connexion SSH se coupe. Attendez une minute et reconnectez-vous
|
||||
(`ssh pixel@vitrine.local`).
|
||||
|
||||
---
|
||||
|
||||
## 7. Ce que fait l'installation (pour comprendre)
|
||||
|
||||
Le script [`installation/installer.sh`](installation/installer.sh) est
|
||||
commenté ligne par ligne. Voici ses 5 étapes, et les commandes qu'on taperait
|
||||
à la main :
|
||||
|
||||
**1. Installer des logiciels du système.** `apt` est le « magasin
|
||||
d'applications » de Raspberry Pi OS :
|
||||
|
||||
```bash
|
||||
sudo apt-get install -y python3-venv python3-pil python3-dev gcc
|
||||
```
|
||||
|
||||
**2. Créer un environnement Python** et y installer la bibliothèque qui
|
||||
parle aux LED. Un *environnement virtuel* est une boîte réservée au projet :
|
||||
on peut y installer des bibliothèques sans risquer d'abîmer le système.
|
||||
|
||||
```bash
|
||||
python3 -m venv --system-site-packages ~/vitrine-env
|
||||
```
|
||||
|
||||
```bash
|
||||
~/vitrine-env/bin/pip install rpi_ws281x
|
||||
```
|
||||
|
||||
C'est pour ça qu'on lance ensuite les programmes avec `~/vitrine-env/bin/python`
|
||||
et pas simplement `python3` : seul ce Python-là connaît `rpi_ws281x`.
|
||||
|
||||
**3. Désactiver la sortie son.** Sur le Pi, le son analogique et le signal des
|
||||
LED utilisent le **même circuit interne** (le générateur « PWM »). On ne peut
|
||||
pas avoir les deux. Dans le fichier de réglages `/boot/firmware/config.txt`,
|
||||
la ligne `dtparam=audio=on` devient `dtparam=audio=off`.
|
||||
|
||||
**4. Créer le dossier des animations** `/boot/firmware/animations` et y
|
||||
copier les exemples. Pourquoi là ? Voir [MODE_VITRINE.md](MODE_VITRINE.md) :
|
||||
c'est la partie de la carte SD que **tous les ordinateurs** savent ouvrir.
|
||||
|
||||
**5. Installer le service** de démarrage automatique (sans l'activer).
|
||||
Voir [MODE_VITRINE.md](MODE_VITRINE.md).
|
||||
|
||||
---
|
||||
|
||||
## 8. Tester
|
||||
|
||||
Branchez la matrice (voir [README, partie 6](README.md#6-brancher-étape-par-étape)),
|
||||
**alimentation d'établi allumée**, puis dans la session SSH :
|
||||
|
||||
```bash
|
||||
cd ~/vitrine
|
||||
```
|
||||
|
||||
```bash
|
||||
sudo ~/vitrine-env/bin/python test_matrice.py
|
||||
```
|
||||
|
||||
Puis, si le test est bon :
|
||||
|
||||
```bash
|
||||
sudo ~/vitrine-env/bin/python lecteur.py
|
||||
```
|
||||
|
||||
**Ctrl + C** pour arrêter. Pour que le lecteur démarre tout seul à chaque
|
||||
allumage, voir [MODE_VITRINE.md](MODE_VITRINE.md).
|
||||
|
||||
---
|
||||
|
||||
## 9. Les commandes de survie du terminal
|
||||
|
||||
| Commande | Effet |
|
||||
|---|---|
|
||||
| `pwd` | « où suis-je ? » : affiche le dossier courant |
|
||||
| `ls` | liste les fichiers du dossier courant |
|
||||
| `cd vitrine` | entre dans le dossier `vitrine` |
|
||||
| `cd ..` | remonte d'un dossier |
|
||||
| `cd ~` | revient au dossier personnel |
|
||||
| `nano matrice.py` | ouvre un fichier dans un éditeur de texte simple (**Ctrl + O** puis Entrée : enregistrer ; **Ctrl + X** : quitter) |
|
||||
| **Ctrl + C** | arrête le programme en cours |
|
||||
| **Tab** | complète automatiquement un nom de fichier (gain de temps énorme !) |
|
||||
| **↑** | rappelle la commande précédente |
|
||||
| `sudo reboot` | redémarre le Pi |
|
||||
| `sudo poweroff` | éteint proprement le Pi (attendre que la LED verte ne clignote plus avant de débrancher) |
|
||||
| `exit` | quitte la session SSH |
|
||||
|
||||
---
|
||||
|
||||
## 10. Problèmes fréquents
|
||||
|
||||
| Symptôme | Cause probable | Solution |
|
||||
|---|---|---|
|
||||
| `ssh: Could not resolve hostname vitrine.local` | Le Pi n'a pas encore fini de démarrer | Attendre 2 minutes de plus |
|
||||
| | Pi et ordinateur sur deux réseaux différents | Les connecter au même Wi-Fi |
|
||||
| | Réseau qui bloque les noms `.local` (fréquent à l'école) | Trouver l'**adresse IP** du Pi (dans l'interface du téléphone en point d'accès, liste des appareils connectés) et faire `ssh pixel@192.168.x.x` |
|
||||
| | Mauvais Wi-Fi saisi dans Imager | Refaire la carte SD (partie 2) |
|
||||
| `Permission denied, please try again.` | Mauvais mot de passe ou nom d'utilisateur | Retaper ; attention au clavier (QWERTY / AZERTY) |
|
||||
| `WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!` | La carte SD a été refaite : le Pi a une nouvelle « empreinte » | `ssh-keygen -R vitrine.local`, puis se reconnecter |
|
||||
| `externally-managed-environment` lors d'un `pip install` | Installation hors de l'environnement virtuel | Utiliser `~/vitrine-env/bin/pip`, pas `pip` |
|
||||
| Erreur de compilation pendant l'installation de `rpi_ws281x` | Outils de compilation manquants | `sudo apt-get install -y python3-dev gcc`, puis relancer le script |
|
||||
| `config.txt introuvable` | Système qui n'est pas Raspberry Pi OS | Refaire la carte SD avec Raspberry Pi OS |
|
||||
@@ -0,0 +1,269 @@
|
||||
# Mode vitrine : une matrice qui tourne toute seule
|
||||
|
||||
Dans la vitrine, personne ne tapera de commande. Il faut que le Pi :
|
||||
|
||||
1. **démarre le lecteur tout seul** quand on l'allume ;
|
||||
2. permette de **changer les animations** facilement ;
|
||||
3. **survive aux coupures de courant** (une vitrine est souvent éteinte le
|
||||
soir par un interrupteur général) ;
|
||||
4. respecte des **horaires**, si besoin.
|
||||
|
||||
Ce guide suppose que l'installation est faite ([INSTALLATION_PI.md](INSTALLATION_PI.md))
|
||||
et que `lecteur.py` fonctionne quand on le lance à la main.
|
||||
|
||||
---
|
||||
|
||||
## Sommaire
|
||||
|
||||
1. [Démarrage automatique](#1-démarrage-automatique)
|
||||
2. [Surveiller et arrêter le lecteur](#2-surveiller-et-arrêter-le-lecteur)
|
||||
3. [Changer les animations](#3-changer-les-animations)
|
||||
4. [Survivre aux coupures de courant](#4-survivre-aux-coupures-de-courant)
|
||||
5. [Horaires d'allumage](#5-horaires-dallumage)
|
||||
6. [Alimentation et installation définitive](#6-alimentation-et-installation-définitive)
|
||||
7. [Entretien et sauvegarde](#7-entretien-et-sauvegarde)
|
||||
8. [En cas de panne dans la vitrine](#8-en-cas-de-panne-dans-la-vitrine)
|
||||
|
||||
---
|
||||
|
||||
## 1. Démarrage automatique
|
||||
|
||||
L'installation a préparé un **service** nommé `vitrine-pixels` : un programme
|
||||
que Linux lance tout seul à chaque démarrage, et **relance s'il s'arrête**
|
||||
(erreur, fichier abîmé…). Son fichier modèle :
|
||||
[`installation/vitrine-pixels.service`](installation/vitrine-pixels.service).
|
||||
|
||||
Pour l'**activer** (une seule fois) :
|
||||
|
||||
```bash
|
||||
sudo systemctl enable --now vitrine-pixels
|
||||
```
|
||||
|
||||
`enable` = démarrer à chaque allumage ; `--now` = et aussi tout de suite.
|
||||
Débranchez et rebranchez le Pi : environ **30 secondes** après, les
|
||||
animations démarrent.
|
||||
|
||||
Pour le **désactiver** (retour au lancement à la main) :
|
||||
|
||||
```bash
|
||||
sudo systemctl disable --now vitrine-pixels
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Surveiller et arrêter le lecteur
|
||||
|
||||
| Je veux… | Commande |
|
||||
|---|---|
|
||||
| savoir si le lecteur tourne | `systemctl status vitrine-pixels` (`active (running)` = oui ; **q** pour quitter) |
|
||||
| voir ce qu'il affiche, en direct | `journalctl -u vitrine-pixels -f` (**Ctrl + C** pour quitter) |
|
||||
| l'arrêter jusqu'au prochain démarrage | `sudo systemctl stop vitrine-pixels` |
|
||||
| le relancer (après avoir modifié un réglage) | `sudo systemctl restart vitrine-pixels` |
|
||||
|
||||
> ⚠️ **Un seul programme à la fois** peut parler à la matrice. Avant de lancer
|
||||
> `test_matrice.py`, `mes_animations.py` ou `lecteur.py` à la main, **arrêtez
|
||||
> le service** (`sudo systemctl stop vitrine-pixels`), sinon les deux
|
||||
> programmes se battent et la matrice affiche n'importe quoi.
|
||||
|
||||
---
|
||||
|
||||
## 3. Changer les animations
|
||||
|
||||
Le lecteur lit le dossier **`/boot/firmware/animations`** (ou
|
||||
**`/boot/animations`** sur les systèmes plus anciens comme Raspbian 11). Ce dossier se trouve
|
||||
sur la petite partie de la carte SD nommée **`bootfs`** (**`boot`** sur les
|
||||
systèmes plus anciens), formatée en FAT32 :
|
||||
le format des clés USB, que **Windows, macOS et Linux** savent tous lire et
|
||||
écrire. C'est pour ça qu'on l'a choisi.
|
||||
|
||||
Le dossier est **relu à chaque tour de boucle** : pas besoin de redémarrer
|
||||
le lecteur après un changement.
|
||||
|
||||
### Méthode 1 : la carte SD (sans réseau, sans terminal)
|
||||
|
||||
C'est la méthode à donner à l'équipe design.
|
||||
|
||||
1. Éteignez le Pi **proprement** : `sudo poweroff` en SSH, ou… si le Pi est
|
||||
protégé par l'*overlay* (partie 4), on peut simplement couper le courant.
|
||||
2. Retirez la carte micro-SD du Pi et mettez-la dans l'ordinateur.
|
||||
3. Un lecteur nommé **`bootfs`** apparaît. Ouvrez le dossier **`animations`**.
|
||||
4. Ajoutez, supprimez ou renommez des animations (voir
|
||||
[GUIDE_KRITA.md, partie 8](GUIDE_KRITA.md#8-déposer-lanimation)).
|
||||
⚠️ **Ne touchez à rien d'autre** sur `bootfs` : ce sont les fichiers de
|
||||
démarrage du Pi.
|
||||
5. **Éjectez** la carte proprement (clic droit → Éjecter), remettez-la dans
|
||||
le Pi, rallumez.
|
||||
|
||||
> **Windows propose de « formater » la carte ?** C'est l'autre partie de la
|
||||
> carte (le système Linux), que Windows ne sait pas lire. Répondez
|
||||
> **Annuler**, jamais Formater !
|
||||
|
||||
> **macOS** crée des fichiers cachés (`._coeur.gif`, `.DS_Store`) : le
|
||||
> lecteur les ignore, pas d'inquiétude.
|
||||
|
||||
### Méthode 2 : par le réseau
|
||||
|
||||
Depuis l'ordinateur, sur le même Wi-Fi que le Pi. Le dossier appartient à
|
||||
l'administrateur : on copie d'abord dans le dossier personnel, puis on
|
||||
déplace avec `sudo`.
|
||||
|
||||
```bash
|
||||
scp -r 05_feu_de_camp 06_logo.png pixel@vitrine.local:~/
|
||||
```
|
||||
|
||||
Puis, dans une session SSH sur le Pi :
|
||||
|
||||
```bash
|
||||
sudo mv ~/05_feu_de_camp ~/06_logo.png /boot/firmware/animations/
|
||||
```
|
||||
|
||||
Pour voir ce qu'il y a dans le dossier :
|
||||
|
||||
```bash
|
||||
ls /boot/firmware/animations
|
||||
```
|
||||
|
||||
Pour retirer une animation :
|
||||
|
||||
```bash
|
||||
sudo rm -r /boot/firmware/animations/05_feu_de_camp
|
||||
```
|
||||
|
||||
> Cette méthode ne marche que si la partition `bootfs` n'est **pas protégée
|
||||
> en écriture** (partie 4).
|
||||
|
||||
---
|
||||
|
||||
## 4. Survivre aux coupures de courant
|
||||
|
||||
### Le problème
|
||||
|
||||
Une carte SD n'aime pas qu'on lui coupe le courant **pendant qu'elle écrit**.
|
||||
Or Linux écrit régulièrement sur la carte (journaux, fichiers temporaires…).
|
||||
Une coupure au mauvais moment peut abîmer des fichiers, et un jour le Pi ne
|
||||
redémarre plus. Pour un ordinateur qu'on éteint proprement, aucun souci ;
|
||||
pour une vitrine coupée chaque soir à l'interrupteur, c'est une question de
|
||||
semaines.
|
||||
|
||||
### La solution : l'« overlay » (système en lecture seule)
|
||||
|
||||
Raspberry Pi OS sait rendre la carte SD **intouchable** : toutes les écritures
|
||||
vont dans la mémoire vive (la RAM), et disparaissent à l'extinction. La carte
|
||||
SD n'est plus jamais modifiée : on peut couper le courant n'importe quand.
|
||||
|
||||
C'est comme une **ardoise magique** posée sur la carte : on peut écrire
|
||||
dessus, mais tout s'efface au redémarrage.
|
||||
|
||||
**Pour l'activer**, en SSH :
|
||||
|
||||
```bash
|
||||
sudo raspi-config
|
||||
```
|
||||
|
||||
1. Avec les flèches : **Performance Options** → **Overlay File System**.
|
||||
2. « Would you like the overlay file system to be enabled? » → **Oui**.
|
||||
3. « Would you like the boot partition to be write-protected? » :
|
||||
- **Oui** (conseillé pour la vitrine) : protection totale ; on change
|
||||
alors les animations par la **méthode 1** (carte SD dans l'ordinateur),
|
||||
qui fonctionne toujours ;
|
||||
- **Non** : la méthode 2 (réseau) reste possible.
|
||||
4. **Finish**, puis redémarrer.
|
||||
|
||||
### ⚠️ Le piège de l'ardoise magique
|
||||
|
||||
Avec l'overlay activé, **toute modification faite sur le Pi est perdue au
|
||||
redémarrage** : un réglage changé dans `matrice.py`, un fichier copié dans
|
||||
`~/vitrine`… Pour modifier durablement quelque chose :
|
||||
|
||||
1. `sudo raspi-config` → désactiver l'Overlay File System → redémarrer ;
|
||||
2. faire ses modifications, tester ;
|
||||
3. réactiver l'overlay → redémarrer.
|
||||
|
||||
**Règle simple** : on règle tout (orientation, luminosité, horaires) **avant**
|
||||
d'activer l'overlay, au moment de l'installation dans la vitrine.
|
||||
|
||||
---
|
||||
|
||||
## 5. Horaires d'allumage
|
||||
|
||||
Deux façons de faire :
|
||||
|
||||
### A. Une prise programmable (conseillé)
|
||||
|
||||
Une **prise minuteur** (mécanique ou numérique, quelques euros) coupe le
|
||||
courant du Pi **et** de l'alimentation des LED la nuit. Simple, fiable,
|
||||
économe : la nuit, rien ne consomme. **Il faut alors l'overlay** (partie 4),
|
||||
puisque le courant est coupé brutalement chaque soir.
|
||||
|
||||
### B. Le réglage `HORAIRES` du lecteur
|
||||
|
||||
En haut de [`lecteur.py`](lecteur.py) :
|
||||
|
||||
```python
|
||||
HORAIRES = ("09:00", "22:30")
|
||||
```
|
||||
|
||||
En dehors de ces heures, la matrice s'éteint (le Pi, lui, reste allumé).
|
||||
Les horaires qui passent minuit fonctionnent : `("18:00", "02:00")`.
|
||||
|
||||
> ⚠️ **Le Pi n'a pas d'horloge à pile** (sauf le Pi 5). Il met son heure à
|
||||
> jour **par Internet** au démarrage. Sans réseau dans la vitrine, son heure
|
||||
> est fausse et les horaires ne marchent pas. Dans ce cas : prise
|
||||
> programmable.
|
||||
|
||||
---
|
||||
|
||||
## 6. Alimentation et installation définitive
|
||||
|
||||
L'alimentation d'établi, c'est pour l'atelier. Dans la vitrine :
|
||||
|
||||
- **Alimentation 5 V fixe** pour les LED, d'au moins **5 A** pour la matrice
|
||||
8 × 32 à luminosité modérée, **10 A** pour la matrice finale 7 × 39. Préférez
|
||||
un **bloc secteur fermé** (comme un chargeur d'ordinateur, avec une prise
|
||||
jack) à une alimentation « nue » à vis : sur ces dernières, le 230 V est
|
||||
accessible.
|
||||
⚠️ **Tout câblage en 230 V doit être fait ou vérifié par une personne
|
||||
compétente.**
|
||||
- Réglez `COURANT_MAX_MA` dans `matrice.py` à environ **80 %** de ce que
|
||||
fournit l'alimentation (4000 pour un bloc de 5 A).
|
||||
- **Luminosité** : en vitrine, en plein jour, il faut souvent monter
|
||||
`LUMINOSITE` à 100, voire 150. Regardez depuis la rue, à différentes heures.
|
||||
Plus de luminosité = plus de courant : vérifiez avec `verifier_animation.py`.
|
||||
- **Fils** : remplacez la plaque d'essai par une **plaque à pastilles soudée**
|
||||
(ou au minimum, collez les fils au pistolet à colle) : les vibrations et les
|
||||
manipulations finissent toujours par débrancher un fil Dupont.
|
||||
- **Chaleur** : derrière une vitre au soleil, il peut faire plus de 50 °C.
|
||||
Laissez de l'air autour du Pi et de l'alimentation. Pour voir la
|
||||
température du Pi : `vcgencmd measure_temp` (au-delà de 80 °C, il ralentit).
|
||||
- **Wi-Fi** : si la vitrine a du Wi-Fi, on peut garder un accès SSH pour
|
||||
dépanner à distance. Sinon, la méthode « carte SD » suffit.
|
||||
|
||||
---
|
||||
|
||||
## 7. Entretien et sauvegarde
|
||||
|
||||
- **Gardez une carte SD de secours** prête, identique à celle de la vitrine
|
||||
(même installation). En cas de panne, on échange les cartes en une minute.
|
||||
- **Gardez une copie des animations** ailleurs que sur la carte SD (sur un
|
||||
ordinateur ou un disque partagé de l'équipe).
|
||||
- Pour faire une **copie exacte** d'une carte SD (une « image ») : logiciels
|
||||
**Win32 Disk Imager** (Windows), **ApplePi-Baker** (macOS), ou la commande
|
||||
`dd` (Linux). On peut ensuite réécrire cette image sur une nouvelle carte
|
||||
avec Raspberry Pi Imager (*Use custom*).
|
||||
|
||||
---
|
||||
|
||||
## 8. En cas de panne dans la vitrine
|
||||
|
||||
| Symptôme | Vérifier | Solution |
|
||||
|---|---|---|
|
||||
| Rien ne s'allume du tout | La LED rouge du Pi est-elle allumée ? | Prise, prise programmable, bloc secteur du Pi |
|
||||
| | L'alimentation des LED est-elle branchée ? | La puce 74AHCT125 est alimentée par elle : sans elle, aucun signal |
|
||||
| Le Pi est allumé mais la matrice reste noire | Le service tourne-t-il ? `systemctl status vitrine-pixels` | `sudo systemctl restart vitrine-pixels` ; lire le journal (partie 2) |
|
||||
| | Les horaires sont-ils corrects ? L'heure du Pi est-elle bonne ? (`date`) | Corriger `HORAIRES` ou mettre `None` |
|
||||
| | Le dossier d'animations est-il vide ? | Un point bleu-vert qui respire en bas à droite = « je n'ai rien à jouer » |
|
||||
| Une animation est sautée | Journal : `✗ impossible de lire ce fichier` | Réexporter le fichier depuis Krita |
|
||||
| Les nouvelles animations n'apparaissent pas | Ont-elles été copiées dans `bootfs/animations` (et pas ailleurs) ? | Refaire la copie, éjecter proprement la carte |
|
||||
| | Overlay activé et copie faite par le réseau ? | Avec l'overlay, la copie réseau est perdue au redémarrage : passer par la carte SD |
|
||||
| Le Pi ne démarre plus (LED verte immobile) | Carte SD abîmée | Remettre la carte de secours ; refaire une carte (et activer l'overlay !) |
|
||||
| Couleurs aléatoires après un transport | Un fil s'est débranché ou desserré | Revoir le montage avec le [README](README.md#6-brancher-étape-par-étape) |
|
||||
@@ -0,0 +1,446 @@
|
||||
# Matrice LED 8 × 32 (WS2812B) avec un Raspberry Pi
|
||||
|
||||
Workshop « Vitrine pixels » : la même matrice de **256 pixels** que dans le
|
||||
dossier [`matrice_8x32`](../matrice_8x32/README.md), mais pilotée par un
|
||||
**Raspberry Pi**. L'avantage : le Pi sait **lire des images**. Les animations
|
||||
dessinées dans **Krita** sont jouées telles quelles, sans les traduire en code.
|
||||
|
||||

|
||||
|
||||
> **À qui s'adresse ce dossier ?**
|
||||
> - **L'équipe électronique** : ce README (montage, test, dépannage), puis
|
||||
> [INSTALLATION_PI.md](INSTALLATION_PI.md) (préparer le Pi).
|
||||
> - **L'équipe design** : [GUIDE_KRITA.md](GUIDE_KRITA.md) (dessiner et exporter
|
||||
> pour la matrice). Pas besoin de toucher au matériel !
|
||||
> - **Pour la vitrine** : [MODE_VITRINE.md](MODE_VITRINE.md) (démarrage
|
||||
> automatique, changer les animations, coupures de courant).
|
||||
> - **Pour qui veut coder** : [CODER_EN_PYTHON.md](CODER_EN_PYTHON.md).
|
||||
|
||||
---
|
||||
|
||||
## Sommaire
|
||||
|
||||
1. [Arduino ou Raspberry Pi ?](#1-arduino-ou-raspberry-pi-)
|
||||
2. [Le matériel](#2-le-matériel)
|
||||
3. [Comment ça marche ?](#3-comment-ça-marche-)
|
||||
4. [Le problème des 3,3 V et le 74AHCT125](#4-le-problème-des-33-v-et-le-74ahct125)
|
||||
5. [Les 5 règles d'or](#5-les-5-règles-dor)
|
||||
6. [Brancher, étape par étape](#6-brancher-étape-par-étape)
|
||||
7. [Préparer le Raspberry Pi](#7-préparer-le-raspberry-pi)
|
||||
8. [Premier allumage : le test](#8-premier-allumage--le-test)
|
||||
9. [Jouer les animations](#9-jouer-les-animations)
|
||||
10. [Ça ne marche pas ?](#10-ça-ne-marche-pas-)
|
||||
11. [Petit lexique](#11-petit-lexique)
|
||||
12. [Les fichiers du dossier](#12-les-fichiers-du-dossier)
|
||||
|
||||
---
|
||||
|
||||
## 1. Arduino ou Raspberry Pi ?
|
||||
|
||||
Ce sont deux cartes très différentes :
|
||||
|
||||
- L'**Arduino** est un **microcontrôleur** : une petite puce qui exécute un
|
||||
seul programme, en boucle, dès qu'on l'alimente. Il est simple et robuste,
|
||||
mais minuscule : 2 Ko de mémoire de travail et 32 Ko pour le programme.
|
||||
- Le **Raspberry Pi** est un vrai **ordinateur**, de la taille d'une carte de
|
||||
crédit, avec un système (Linux), des fichiers, le Wi-Fi, une carte SD de
|
||||
plusieurs gigaoctets…
|
||||
|
||||
| | Arduino Uno | Raspberry Pi |
|
||||
|---|---|---|
|
||||
| Passer d'un dessin Krita à la matrice | dessin → script de conversion → code C → téléversement | copier le GIF dans un dossier |
|
||||
| Nombre d'images possibles | ≈ 30 (une image = 768 octets, la mémoire est pleine très vite) | des milliers (carte SD) |
|
||||
| Changer les animations | ordinateur + câble USB + IDE Arduino | carte SD, ou par le réseau |
|
||||
| Démarrage | instantané | ≈ 30 secondes |
|
||||
| Coupure de courant brutale | aucun problème | risque d'abîmer la carte SD (solution : [MODE_VITRINE.md](MODE_VITRINE.md)) |
|
||||
| Langage | C++ | Python |
|
||||
| Niveau électrique de la broche de données | 5 V : parfait pour les LED | **3,3 V : trop faible**, il faut un adaptateur (partie 4) |
|
||||
|
||||
**Conclusion** : pour **apprendre** (comprendre les bits, le multiplexage, la
|
||||
chaîne de LED), l'Arduino est idéal. Pour **diffuser les créations d'une
|
||||
équipe design** dans une vitrine, le Raspberry Pi est beaucoup plus pratique.
|
||||
|
||||
---
|
||||
|
||||
## 2. Le matériel
|
||||
|
||||
| Quoi | Combien | Remarque |
|
||||
|---|---|---|
|
||||
| Matrice LED WS2812B 8 × 32, souple | 1 | la même que dans `matrice_8x32` |
|
||||
| **Raspberry Pi 3B+, 4 ou Zero 2 W** | 1 | ⚠️ **pas le Pi 5** (voir encadré) |
|
||||
| Bloc secteur USB **officiel** du Pi | 1 | micro-USB 2,5 A (3B+, Zero 2 W) ou USB-C 3 A (Pi 4) |
|
||||
| Carte micro-SD 16 Go ou plus | 1 | « classe 10 » / « A1 » |
|
||||
| Puce **74AHCT125** (boîtier DIP 14 pattes) | 1 | l'adaptateur 3,3 V → 5 V (partie 4). Le **74HCT125** convient aussi |
|
||||
| Alimentation d'établi | 1 | réglée sur **5,0 V**, limite ≈ 3 A |
|
||||
| 2 cordons banane → pince crocodile | 1 rouge + 1 noir | |
|
||||
| Résistance **330 Ω** | 1 | orange-orange-marron |
|
||||
| Condensateur électrolytique **1000 µF**, 10 V ou plus | 1 | |
|
||||
| Plaque d'essai (breadboard) | 1 | obligatoire cette fois, pour la puce |
|
||||
| Fils Dupont **mâle-mâle** | 6 | sur la plaque d'essai |
|
||||
| Fils Dupont **femelle-mâle** | 2 | du connecteur du Pi vers la plaque d'essai |
|
||||
| Lecteur de carte SD pour l'ordinateur | 1 | pour préparer la carte |
|
||||
|
||||
> **Pourquoi pas le Raspberry Pi 5 ?** Sur le Pi 5, les broches sont gérées
|
||||
> par une nouvelle puce (« RP1 »), et la bibliothèque `rpi_ws281x` que nous
|
||||
> utilisons ne sait pas encore lui parler. Il existe d'autres bibliothèques
|
||||
> pour le Pi 5, mais elles sont moins simples. Le Pi 4, le Pi 3B+ et le petit
|
||||
> Pi Zero 2 W (≈ 20 €, largement assez puissant) fonctionnent parfaitement.
|
||||
|
||||
---
|
||||
|
||||
## 3. Comment ça marche ?
|
||||
|
||||
Côté **matrice**, rien ne change par rapport à l'Arduino (relisez la
|
||||
[partie 2 du README Arduino](../matrice_8x32/README.md#2-comment-ça-marche-)) :
|
||||
les 256 LED forment une **chaîne**, et on leur envoie une longue liste de
|
||||
couleurs par **un seul fil** de données. L'énergie vient de l'alimentation
|
||||
d'établi.
|
||||
|
||||
Côté **cerveau**, le Raspberry Pi fait tourner le programme
|
||||
[`lecteur.py`](lecteur.py), qui :
|
||||
|
||||
1. regarde ce qu'il y a dans le dossier `animations` ;
|
||||
2. ouvre chaque fichier (GIF, PNG…) et le découpe en images de 32 × 8 ;
|
||||
3. pour chaque image, **corrige les couleurs** pour que la matrice ressemble
|
||||
à l'écran de Krita (la « correction gamma »), applique la luminosité et la
|
||||
limite de courant ;
|
||||
4. remet les pixels dans l'**ordre de la chaîne** (le serpentin, avec la
|
||||
même fonction `XY()` que sur l'Arduino) ;
|
||||
5. envoie les 256 couleurs dans le fil, attend la durée prévue, passe à
|
||||
l'image suivante ;
|
||||
6. recommence avec l'animation suivante, et ainsi de suite, en boucle.
|
||||
|
||||
---
|
||||
|
||||
## 4. Le problème des 3,3 V et le 74AHCT125
|
||||
|
||||
### Le problème
|
||||
|
||||
Les broches du Raspberry Pi fonctionnent en **3,3 V** : un « 1 » envoyé dans le
|
||||
fil de données vaut 3,3 V. Mais une LED WS2812B alimentée en 5 V ne comprend
|
||||
un « 1 » qu'à partir d'environ **3,5 V** (70 % de 5 V).
|
||||
|
||||
3,3 V, c'est juste en dessous. Souvent ça marche quand même… et parfois les
|
||||
couleurs deviennent aléatoires, ou ça scintille, selon la température, la
|
||||
longueur du fil, le lot de LED. Pour une vitrine qui doit tourner des
|
||||
semaines, on ne joue pas à pile ou face.
|
||||
|
||||
> ⚠️ **Ne jamais relier directement le 5 V à une broche du Pi** : les broches du
|
||||
> Pi supportent au maximum 3,3 V. Au-delà, elles grillent, et souvent le Pi
|
||||
> avec. Le courant ne doit circuler que **du Pi vers la matrice**, jamais
|
||||
> l'inverse.
|
||||
|
||||
### La solution : un adaptateur de niveau
|
||||
|
||||
Le **74AHCT125** est une petite puce qui « répète » un signal en le
|
||||
renforçant : elle écoute un 3,3 V faible et ressort un vrai 5 V propre. Elle
|
||||
contient 4 répéteurs (des « portes »). On n'en utilise qu'un seul.
|
||||
|
||||

|
||||
|
||||
| Patte | Nom | Rôle | Branchée sur |
|
||||
|---|---|---|---|
|
||||
| **14** | VCC | alimentation de la puce | **5 V** (fil rouge de la fiche DIN) |
|
||||
| **7** | GND | masse | **GND** |
|
||||
| **1** | 1OE | « marche/arrêt » du répéteur n° 1 (actif quand il est à 0 V) | **GND** (= toujours en marche) |
|
||||
| **2** | 1A | **entrée** du répéteur | **GPIO 18** du Pi (patte 12) |
|
||||
| **3** | 1Y | **sortie** du répéteur | résistance 330 Ω → fil **vert** (DIN) |
|
||||
| 4 à 6, 8 à 13 | | répéteurs 2, 3, 4 | rien (on peut relier 4, 10 et 13 au 5 V pour les éteindre) |
|
||||
|
||||
**Repérer la patte 1** : le boîtier a une **encoche** (demi-lune) à une
|
||||
extrémité, et souvent un petit **point** à côté de la patte 1. Encoche à
|
||||
gauche, la patte 1 est **en bas à gauche**. On compte ensuite dans le sens
|
||||
inverse des aiguilles d'une montre : 1 à 7 en bas, de gauche à droite, puis
|
||||
8 à 14 en haut, de droite à gauche.
|
||||
|
||||
> **Pourquoi « AHCT » et pas « HC » ?** Les lettres **T** (comme dans HCT,
|
||||
> AHCT) veulent dire que la puce accepte des entrées « faibles » de 3,3 V même
|
||||
> quand elle est alimentée en 5 V. Un 74**HC**125, lui, ne serait pas plus
|
||||
> malin que les LED. Vérifiez bien les lettres écrites sur la puce.
|
||||
|
||||
---
|
||||
|
||||
## 5. Les 5 règles d'or
|
||||
|
||||
1. **5 volts, pas plus.** Au-delà de 5,5 V, les LED grillent. On règle
|
||||
l'alimentation d'établi **avant** de brancher.
|
||||
2. **Jamais le 5 V du Pi vers la matrice.** Les pattes 2 et 4 du Pi (5 V)
|
||||
ne sont reliées à **rien**. La matrice est alimentée **uniquement** par
|
||||
l'alimentation d'établi.
|
||||
3. **Jamais de 5 V sur une broche du Pi.** Seule la patte 12 (GPIO 18) et la
|
||||
patte 6 (GND) sont branchées. Le 5 V arrive à la puce, pas au Pi.
|
||||
4. **Toutes les masses (GND) reliées ensemble** : Pi, puce, matrice et
|
||||
alimentation d'établi.
|
||||
5. **On câble hors tension**, Pi éteint, et on fait vérifier avant d'allumer.
|
||||
|
||||
---
|
||||
|
||||
## 6. Brancher, étape par étape
|
||||
|
||||

|
||||
|
||||
**Tout est débranché** : alimentation d'établi éteinte, Pi sans son bloc
|
||||
secteur.
|
||||
|
||||
### Étape A : l'énergie de la matrice
|
||||
|
||||
Exactement comme avec l'Arduino ([README Arduino, parties 5 et 6 A](../matrice_8x32/README.md#5-régler-lalimentation-détabli)) :
|
||||
|
||||
1. Réglez l'alimentation d'établi sur **5,0 V**, limite de courant **3 A**,
|
||||
puis éteignez-la.
|
||||
2. Cordon **rouge** : borne **+** → fil **rouge** du milieu de la matrice.
|
||||
3. Cordon **noir** : borne **−** → fil **noir** du milieu.
|
||||
4. **Condensateur 1000 µF** entre ces deux fils (bande « − » côté noir).
|
||||
|
||||
### Étape B : la puce sur la plaque d'essai
|
||||
|
||||
5. Enfoncez le **74AHCT125** à cheval sur la **rainure centrale** de la plaque
|
||||
d'essai, encoche à gauche. (Chaque patte doit avoir sa propre rangée de
|
||||
trous ; sans la rainure, les pattes d'en face seraient reliées entre elles.)
|
||||
6. Branchez la **fiche** sur le connecteur **`DIN`** de la matrice.
|
||||
7. Fil **rouge** de la fiche → rangée de la **patte 14** (VCC).
|
||||
*Avec l'Arduino, ce fil restait isolé. Ici, il apporte le 5 V de la matrice
|
||||
à la puce. Une puce consomme à peine quelques milliampères : aucun risque.*
|
||||
8. Fil **blanc** de la fiche → une **ligne de masse** de la plaque d'essai
|
||||
(la bande bleue « − » sur le côté).
|
||||
9. Patte **7** (GND) → ligne de masse (fil mâle-mâle).
|
||||
10. Patte **1** (1OE) → ligne de masse (fil mâle-mâle).
|
||||
11. **Résistance 330 Ω** : une patte dans la rangée de la **patte 3** (1Y),
|
||||
l'autre dans une rangée libre.
|
||||
12. Fil **vert** de la fiche → la rangée libre de la résistance.
|
||||
|
||||
### Étape C : le Raspberry Pi
|
||||
|
||||
Le connecteur du Pi a **40 pattes** sur deux rangées. La **patte 1** est celle
|
||||
qui a une **pastille carrée** au dos du circuit, dans le coin. Les pattes
|
||||
**impaires** (1, 3, 5…) sont sur la rangée intérieure, les **paires** (2, 4,
|
||||
6…) sur la rangée du bord.
|
||||
|
||||
```
|
||||
bord de la carte
|
||||
┌──────────────────────────────────────
|
||||
│ 2 4 6 8 10 12 14 … 40 ← rangée du bord (paires)
|
||||
│ 5V 5V GND GPIO18
|
||||
│ 1 3 5 7 9 11 13 … 39 ← rangée intérieure (impaires)
|
||||
│ 3V3
|
||||
```
|
||||
|
||||
13. Fil femelle-mâle : patte **6** (GND) du Pi → ligne de masse de la plaque.
|
||||
14. Fil femelle-mâle : patte **12** (GPIO 18) du Pi → rangée de la **patte 2**
|
||||
(1A) de la puce.
|
||||
|
||||
### Étape D : vérifier
|
||||
|
||||
- [ ] Alimentation d'établi réglée sur 5,0 V, limite de courant réglée
|
||||
- [ ] + rouge → fil rouge du milieu ; − noir → fil noir du milieu ; condensateur dans le bon sens
|
||||
- [ ] Fiche sur **`DIN`** (pas `DOUT`)
|
||||
- [ ] Puce à cheval sur la rainure, **encoche à gauche**
|
||||
- [ ] Rouge de la fiche → patte 14 ; blanc → masse
|
||||
- [ ] Pattes 1 et 7 → masse
|
||||
- [ ] Patte 3 → 330 Ω → vert de la fiche
|
||||
- [ ] Pi : patte 12 → patte 2 de la puce ; patte 6 → masse
|
||||
- [ ] **Rien** sur les pattes 1, 2, 4 (3,3 V et 5 V) du Pi
|
||||
- [ ] Montage vérifié par l'encadrant
|
||||
|
||||
### Ordre d'allumage et d'extinction
|
||||
|
||||
| Allumer | Éteindre |
|
||||
|---|---|
|
||||
| 1. l'alimentation d'établi | 1. arrêter le Pi proprement (`sudo poweroff`), attendre que la LED verte s'éteigne |
|
||||
| 2. puis le bloc secteur du Pi | 2. débrancher le bloc secteur du Pi |
|
||||
| | 3. éteindre l'alimentation d'établi |
|
||||
|
||||
> **Bonus du montage** : la puce est alimentée **par la matrice** (fil rouge
|
||||
> de la fiche). Si l'alimentation d'établi est éteinte, la puce l'est aussi et
|
||||
> ne fait plus passer le signal : la première LED est protégée.
|
||||
|
||||
---
|
||||
|
||||
## 7. Préparer le Raspberry Pi
|
||||
|
||||
C'est l'objet d'un guide à part : **[INSTALLATION_PI.md](INSTALLATION_PI.md)**.
|
||||
En résumé :
|
||||
|
||||
1. installer **Raspberry Pi OS Lite** sur la carte SD avec **Raspberry Pi Imager** ;
|
||||
2. se connecter au Pi depuis son ordinateur (**SSH**) ;
|
||||
3. copier ce dossier sur le Pi ;
|
||||
4. lancer `bash installation/installer.sh`, qui installe tout le reste.
|
||||
|
||||
Comptez une heure la première fois.
|
||||
|
||||
---
|
||||
|
||||
## 8. Premier allumage : le test
|
||||
|
||||
Sur le Pi (connecté en SSH), dans le dossier du projet :
|
||||
|
||||
```bash
|
||||
sudo ~/vitrine-env/bin/python test_matrice.py
|
||||
```
|
||||
|
||||
> **Pourquoi `sudo` ?** Pour envoyer un signal aussi rapide et précis
|
||||
> (800 000 bits par seconde), la bibliothèque utilise directement des
|
||||
> circuits internes du Pi. Linux réserve cet accès à l'**administrateur** :
|
||||
> `sudo` veut dire « exécuter en tant qu'administrateur ».
|
||||
|
||||
Le test se déroule en trois temps (voir [`test_matrice.py`](test_matrice.py)) :
|
||||
|
||||
**1. Les coins** (5 secondes). Posez la matrice **face LED vers vous** :
|
||||
|
||||
```
|
||||
ROUGE ●· · · · · · · · · · · · · · · · ● VERT
|
||||
· · · · · · · · · · · · · · · · ·
|
||||
· · · · · · · · · · · · · · · · ·
|
||||
BLEU ●· · · · · · · · · · · · · · · · ● BLANC
|
||||
```
|
||||
|
||||
| Ce que vous voyez | Ce qu'il faut faire, dans [`matrice.py`](matrice.py) |
|
||||
|---|---|
|
||||
| Rouge en haut à gauche, vert en haut à droite | rien, c'est parfait ✅ |
|
||||
| Rouge et vert inversés (gauche ↔ droite) | `INVERSER_X = True` |
|
||||
| Rouge et vert en bas | `INVERSER_Y = True` |
|
||||
| Les deux à la fois | les deux à `True` |
|
||||
|
||||
Pour modifier le fichier sur le Pi : `nano matrice.py`, changez la ligne,
|
||||
puis **Ctrl + O**, **Entrée** (enregistrer) et **Ctrl + X** (quitter).
|
||||
|
||||
**2. Le serpentin** : un point orange parcourt les 256 LED dans l'ordre de la
|
||||
chaîne.
|
||||
|
||||
**3. Les couleurs pleines** : rouge, vert, bleu, puis blanc. Si le rouge
|
||||
apparaît vert, l'ordre des couleurs est faux (voir partie 10).
|
||||
|
||||
---
|
||||
|
||||
## 9. Jouer les animations
|
||||
|
||||
```bash
|
||||
sudo ~/vitrine-env/bin/python lecteur.py
|
||||
```
|
||||
|
||||
Le lecteur joue en boucle, **par ordre alphabétique**, tout ce qu'il trouve
|
||||
dans le dossier des animations :
|
||||
|
||||
| Format | Exemple | Remarque |
|
||||
|---|---|---|
|
||||
| GIF animé | `01_coeur_qui_bat.gif` | le plus simple ; la vitesse est dans le fichier |
|
||||
| PNG animé (APNG) ou WebP animé | `04_balle.png` | plus de couleurs que le GIF |
|
||||
| Séquence de PNG | `03_vague/` contenant `vague_0000.png`, `vague_0001.png`… | la vitesse est dans `images_par_seconde.txt` (sinon 12) |
|
||||
| Image fixe | `logo.png` | affichée 10 secondes |
|
||||
|
||||
Le dossier lu est **`/boot/firmware/animations`** s'il existe (créé par
|
||||
l'installation, voir [MODE_VITRINE.md](MODE_VITRINE.md)), sinon le dossier
|
||||
[`animations`](animations/) à côté du programme. Il est **relu à chaque tour** :
|
||||
on peut ajouter un GIF pendant que ça tourne.
|
||||
|
||||
Pour arrêter : **Ctrl + C**.
|
||||
|
||||
Les réglages sont en haut de [`lecteur.py`](lecteur.py) (durée minimum par
|
||||
animation, pause entre deux, horaires) et de [`matrice.py`](matrice.py) :
|
||||
|
||||
| Réglage | Fichier | Valeur | À quoi ça sert |
|
||||
|---|---|---|---|
|
||||
| `LUMINOSITE` | matrice.py | `40` | de 0 à 255 ; montez en plein jour, en vitrine |
|
||||
| `COURANT_MAX_MA` | matrice.py | `2500` | un peu **sous** la limite de l'alimentation |
|
||||
| `INVERSER_X`, `INVERSER_Y` | matrice.py | `False` | orientation (partie 8) |
|
||||
| `GAMMA` | matrice.py | `2.2` | correction des couleurs (1.0 = aucune) |
|
||||
| `DUREE_MINIMUM` | lecteur.py | `10` | une animation courte est rejouée pendant au moins 10 s |
|
||||
| `HORAIRES` | lecteur.py | `None` | ex. `("09:00", "22:30")` |
|
||||
|
||||
### Sans matériel : le simulateur
|
||||
|
||||
Sur **n'importe quel ordinateur** avec Python et Pillow (voir
|
||||
[GUIDE_KRITA.md, partie 7](GUIDE_KRITA.md#7-prévisualiser-sur-son-ordinateur)) :
|
||||
|
||||
```bash
|
||||
python3 lecteur.py --simulateur
|
||||
```
|
||||
|
||||
Une fenêtre s'ouvre avec 256 ronds de couleur qui imitent la matrice.
|
||||
|
||||
---
|
||||
|
||||
## 10. Ça ne marche pas ?
|
||||
|
||||
| Symptôme | Causes probables | Solution |
|
||||
|---|---|---|
|
||||
| **Rien ne s'allume** | Fiche sur `DOUT` au lieu de `DIN` | Changer de côté |
|
||||
| | Alimentation d'établi éteinte | L'allumer, activer `OUTPUT` (la puce aussi est alimentée par là) |
|
||||
| | Puce à l'envers | Encoche à gauche, patte 1 en bas à gauche |
|
||||
| | Fil sur la mauvaise patte du Pi | Patte **12** = GPIO 18 ; recompter depuis la patte 1 |
|
||||
| | Pas de GND commun | Patte 6 du Pi, patte 7 et patte 1 de la puce, fil blanc : tous sur la même ligne de masse |
|
||||
| **`ws2811_init failed`** ou **`Permission denied`** | Programme lancé sans `sudo` | Ajouter `sudo` devant la commande |
|
||||
| **`ModuleNotFoundError: rpi_ws281x`** | Mauvais Python | Utiliser `~/vitrine-env/bin/python`, pas `python3` |
|
||||
| | Installation incomplète | Relancer `bash installation/installer.sh` |
|
||||
| **Le simulateur s'ouvre au lieu de la matrice** | Même cause : `rpi_ws281x` introuvable | Idem |
|
||||
| **Ça marche, puis tout se fige ou scintille quand on branche un casque / lance un son** | La sortie audio utilise le même circuit que GPIO 18 | Vérifier `dtparam=audio=off` dans `/boot/firmware/config.txt`, redémarrer |
|
||||
| **Couleurs aléatoires, scintillement** | Puce absente ou de type HC (sans T) | Vérifier les lettres sur la puce |
|
||||
| | Masse mal serrée | Revérifier toutes les masses |
|
||||
| **Couleurs fausses** (rouge ↔ vert) | Ordre des couleurs | Dans `matrice.py`, remplacer `WS2811_STRIP_GRB` par `WS2811_STRIP_RGB` |
|
||||
| **Les couleurs sombres de mon dessin n'apparaissent pas** | Normal : correction gamma + luminosité faible | Voir [GUIDE_KRITA.md, partie 5](GUIDE_KRITA.md#5-ce-qui-change-entre-lécran-et-les-led) |
|
||||
| **Un fichier est sauté** (`✗ impossible de lire`) | Fichier abîmé ou format inconnu (MP4, KRA…) | Réexporter en GIF ou PNG |
|
||||
| **La matrice devient orangée quand tout s'allume** | L'alimentation atteint sa limite (voyant `CC`) | Baisser `LUMINOSITE` ou `COURANT_MAX_MA` |
|
||||
| **Le Pi redémarre tout seul, éclair jaune à l'écran** | Bloc secteur du Pi trop faible | Utiliser le bloc officiel |
|
||||
| **Ça chauffe, ça sent le brûlé** | Court-circuit, condensateur ou puce à l'envers | **Tout couper immédiatement**, appeler l'encadrant |
|
||||
|
||||
Pour voir ce que fait le lecteur quand il tourne tout seul (mode vitrine) :
|
||||
`journalctl -u vitrine-pixels -f` (voir [MODE_VITRINE.md](MODE_VITRINE.md)).
|
||||
|
||||
---
|
||||
|
||||
## 11. Petit lexique
|
||||
|
||||
| Mot | Signification |
|
||||
|---|---|
|
||||
| **Raspberry Pi** | Petit ordinateur complet sur une seule carte |
|
||||
| **Raspberry Pi OS** | Le système d'exploitation du Pi (une version de Linux) |
|
||||
| **Carte SD** | Le « disque dur » du Pi : système, programmes et animations |
|
||||
| **GPIO** | *General Purpose Input/Output* : les pattes du Pi qu'on peut commander par programme |
|
||||
| **3,3 V / 5 V** | Les deux niveaux électriques en jeu : celui du Pi et celui des LED |
|
||||
| **Adaptateur de niveau** | Puce qui traduit un signal 3,3 V en 5 V (ici le 74AHCT125) |
|
||||
| **DIP 14** | Boîtier de puce à 14 pattes, en deux rangées, qui s'enfiche dans une plaque d'essai |
|
||||
| **SSH** | Moyen de « taper des commandes » sur le Pi depuis un autre ordinateur, par le réseau |
|
||||
| **Terminal** | Fenêtre où l'on tape des commandes au lieu de cliquer |
|
||||
| **sudo** | « Exécuter en tant qu'administrateur » |
|
||||
| **Python** | Le langage de programmation utilisé ici |
|
||||
| **Bibliothèque** | Code écrit par d'autres qu'on réutilise (Pillow pour les images, rpi_ws281x pour les LED) |
|
||||
| **Environnement virtuel** | Une boîte qui contient les bibliothèques Python du projet (`~/vitrine-env`) |
|
||||
| **Service** | Programme que Linux lance tout seul au démarrage, et relance s'il s'arrête |
|
||||
| **Correction gamma** | Calcul qui adapte les couleurs d'un écran à la façon dont les LED émettent la lumière |
|
||||
| **GIF, APNG, WebP** | Formats d'images animées |
|
||||
| **Serpentin** | Câblage en zigzag des LED : une colonne descend, la suivante remonte |
|
||||
|
||||
---
|
||||
|
||||
## 12. Les fichiers du dossier
|
||||
|
||||
| Fichier | Pour qui | Contenu |
|
||||
|---|---|---|
|
||||
| [README.md](README.md) | électronique | ce document |
|
||||
| [INSTALLATION_PI.md](INSTALLATION_PI.md) | électronique | préparer la carte SD et le Pi, pas à pas |
|
||||
| [GUIDE_KRITA.md](GUIDE_KRITA.md) | design | dessiner, exporter, vérifier pour la matrice |
|
||||
| [MODE_VITRINE.md](MODE_VITRINE.md) | tous | démarrage automatique, changer les animations, entretien |
|
||||
| [CODER_EN_PYTHON.md](CODER_EN_PYTHON.md) | code | créer des animations en Python ; comparaison avec l'Arduino |
|
||||
| [`matrice.py`](matrice.py) | | le moteur : réglages, `XY()`, gamma, vraie matrice et simulateur |
|
||||
| [`lecteur.py`](lecteur.py) | | joue les animations du dossier en boucle |
|
||||
| [`test_matrice.py`](test_matrice.py) | | test de branchement et d'orientation |
|
||||
| [`mes_animations.py`](mes_animations.py) | | les animations de l'Arduino, traduites en Python |
|
||||
| [`animations/`](animations/) | | exemples : GIF, séquence de PNG, APNG |
|
||||
| `apercus/` | design | créé par `verifier_animation.py` : aperçus « façon LED » à partager |
|
||||
| [`krita/palette_vitrine_pixels.gpl`](krita/palette_vitrine_pixels.gpl) | design | palette de couleurs adaptée aux LED, à importer dans Krita |
|
||||
| [`outils/verifier_animation.py`](outils/verifier_animation.py) | design | vérifie un export et crée un aperçu « façon LED » |
|
||||
| [`outils/generer_exemples.py`](outils/generer_exemples.py) | | recrée les exemples et la palette |
|
||||
| [`outils/schemas.py`](outils/schemas.py) | | redessine les schémas du dossier `schemas/` |
|
||||
| [`installation/`](installation/) | électronique | script d'installation et service de démarrage automatique |
|
||||
|
||||
---
|
||||
|
||||
## Pour aller plus loin
|
||||
|
||||
- **La matrice finale 7 × 39** : changez `LARGEUR`, `HAUTEUR` et la fonction
|
||||
`XY()` dans `matrice.py` ; il faudra une alimentation 5 V de 10 A environ.
|
||||
Les dessins Krita se feront alors en 39 × 7.
|
||||
- **Plusieurs matrices** : la bibliothèque sait piloter deux chaînes à la fois
|
||||
(GPIO 18 et GPIO 13).
|
||||
- **Interactivité** : le Pi peut lire un bouton, un capteur de présence, une
|
||||
page web… et changer d'animation en conséquence.
|
||||
|
After Width: | Height: | Size: 279 B |
|
After Width: | Height: | Size: 12 KiB |
@@ -0,0 +1 @@
|
||||
20
|
||||
|
After Width: | Height: | Size: 171 B |
|
After Width: | Height: | Size: 176 B |
|
After Width: | Height: | Size: 174 B |
|
After Width: | Height: | Size: 174 B |
|
After Width: | Height: | Size: 171 B |
|
After Width: | Height: | Size: 181 B |
|
After Width: | Height: | Size: 166 B |
|
After Width: | Height: | Size: 170 B |
|
After Width: | Height: | Size: 165 B |
|
After Width: | Height: | Size: 163 B |
|
After Width: | Height: | Size: 161 B |
|
After Width: | Height: | Size: 166 B |
|
After Width: | Height: | Size: 160 B |
|
After Width: | Height: | Size: 167 B |
|
After Width: | Height: | Size: 168 B |
|
After Width: | Height: | Size: 172 B |
|
After Width: | Height: | Size: 165 B |
|
After Width: | Height: | Size: 171 B |
|
After Width: | Height: | Size: 169 B |
|
After Width: | Height: | Size: 176 B |
|
After Width: | Height: | Size: 177 B |
|
After Width: | Height: | Size: 166 B |
|
After Width: | Height: | Size: 169 B |
|
After Width: | Height: | Size: 169 B |
|
After Width: | Height: | Size: 170 B |
|
After Width: | Height: | Size: 175 B |
|
After Width: | Height: | Size: 164 B |
|
After Width: | Height: | Size: 168 B |
|
After Width: | Height: | Size: 167 B |
|
After Width: | Height: | Size: 168 B |
|
After Width: | Height: | Size: 167 B |
|
After Width: | Height: | Size: 165 B |
|
After Width: | Height: | Size: 170 B |
|
After Width: | Height: | Size: 170 B |
|
After Width: | Height: | Size: 164 B |
|
After Width: | Height: | Size: 169 B |
|
After Width: | Height: | Size: 166 B |
|
After Width: | Height: | Size: 173 B |
|
After Width: | Height: | Size: 177 B |
|
After Width: | Height: | Size: 177 B |
|
After Width: | Height: | Size: 3.7 KiB |
@@ -0,0 +1,100 @@
|
||||
#!/bin/bash
|
||||
# =============================================================================
|
||||
# INSTALLATION AUTOMATIQUE sur le Raspberry Pi
|
||||
# Workshop « Vitrine pixels »
|
||||
# =============================================================================
|
||||
#
|
||||
# À lancer SUR LE RASPBERRY PI, depuis le dossier du projet :
|
||||
# bash installation/installer.sh
|
||||
#
|
||||
# Ce script fait, dans l'ordre, tout ce qui est décrit à la main dans
|
||||
# INSTALLATION_PI.md (parties 5 à 8). Chaque étape affiche ce qu'elle fait.
|
||||
# On peut le relancer sans danger : ce qui est déjà fait est ignoré.
|
||||
|
||||
set -e # s'arrêter à la première erreur, au lieu de continuer à l'aveugle
|
||||
|
||||
# Le dossier du projet = le dossier parent de celui qui contient ce script.
|
||||
PROJET="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
ENV="$HOME/vitrine-env" # l'« environnement Python » du projet
|
||||
# La partie « boot » de la carte SD (lisible depuis tout ordinateur) est
|
||||
# montée dans /boot/firmware depuis Raspberry Pi OS 12 « Bookworm »,
|
||||
# et dans /boot sur les systèmes plus anciens (Raspbian 11 « Bullseye »).
|
||||
if [ -f /boot/firmware/config.txt ]; then
|
||||
BOOT=/boot/firmware
|
||||
elif [ -f /boot/config.txt ]; then
|
||||
BOOT=/boot
|
||||
else
|
||||
echo "!!! config.txt introuvable : ce script est prévu pour Raspberry Pi OS."
|
||||
exit 1
|
||||
fi
|
||||
CONFIG=$BOOT/config.txt # le fichier de réglages du Pi
|
||||
ANIMATIONS=$BOOT/animations # les animations, lisibles depuis tout ordinateur
|
||||
|
||||
echo "=== Projet : $PROJET"
|
||||
echo "=== Partition de démarrage : $BOOT"
|
||||
|
||||
echo
|
||||
echo "=== 1/5 Installation des logiciels nécessaires (quelques minutes)"
|
||||
# python3-pil = Pillow (lire les images) ; python3-dev et gcc = pour compiler
|
||||
# la bibliothèque rpi_ws281x, qui parle aux LED.
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y python3-venv python3-pil python3-dev gcc
|
||||
|
||||
echo
|
||||
echo "=== 2/5 Environnement Python dans $ENV"
|
||||
# Un « environnement virtuel » est une boîte où l'on installe les
|
||||
# bibliothèques du projet sans toucher au reste du système.
|
||||
# --system-site-packages : la boîte peut aussi utiliser Pillow installé par apt.
|
||||
if [ ! -d "$ENV" ]; then
|
||||
python3 -m venv --system-site-packages "$ENV"
|
||||
fi
|
||||
"$ENV/bin/pip" install --upgrade rpi_ws281x
|
||||
|
||||
echo
|
||||
echo "=== 3/5 Désactivation de la sortie audio (elle utilise le même circuit que les LED)"
|
||||
if grep -q "^dtparam=audio=on" "$CONFIG"; then
|
||||
sudo sed -i 's/^dtparam=audio=on/dtparam=audio=off/' "$CONFIG"
|
||||
echo " audio désactivé (pris en compte au prochain redémarrage)"
|
||||
REDEMARRER=1
|
||||
else
|
||||
echo " déjà fait"
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "=== 4/5 Dossier des animations : $ANIMATIONS"
|
||||
# Il est sur la partie « bootfs » de la carte SD, que Windows, macOS et Linux
|
||||
# savent tous ouvrir : on pourra y copier des GIF depuis n'importe quel ordinateur.
|
||||
sudo mkdir -p "$ANIMATIONS"
|
||||
if [ -z "$(ls -A "$ANIMATIONS")" ]; then
|
||||
sudo cp -r "$PROJET/animations/." "$ANIMATIONS/"
|
||||
echo " exemples copiés"
|
||||
else
|
||||
echo " le dossier contient déjà des animations : on n'y touche pas"
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "=== 5/5 Service de démarrage automatique « vitrine-pixels »"
|
||||
# On recopie le modèle de service en remplaçant @PROJET@ et @PYTHON@
|
||||
# par les vrais chemins.
|
||||
sed -e "s|@PROJET@|$PROJET|g" -e "s|@PYTHON@|$ENV/bin/python|g" \
|
||||
"$PROJET/installation/vitrine-pixels.service" \
|
||||
| sudo tee /etc/systemd/system/vitrine-pixels.service > /dev/null
|
||||
sudo systemctl daemon-reload
|
||||
echo " service installé (pas encore activé)"
|
||||
|
||||
echo
|
||||
echo "================================================================"
|
||||
echo " Installation terminée."
|
||||
echo
|
||||
echo " 1. Testez d'abord la matrice :"
|
||||
echo " sudo $ENV/bin/python $PROJET/test_matrice.py"
|
||||
echo " 2. Puis le lecteur :"
|
||||
echo " sudo $ENV/bin/python $PROJET/lecteur.py"
|
||||
echo " 3. Quand tout marche, démarrage automatique à chaque allumage :"
|
||||
echo " sudo systemctl enable --now vitrine-pixels"
|
||||
if [ -n "$REDEMARRER" ]; then
|
||||
echo
|
||||
echo " ⚠ Redémarrez d'abord le Pi (sudo reboot) : le réglage audio"
|
||||
echo " ne sera pris en compte qu'après."
|
||||
fi
|
||||
echo "================================================================"
|
||||
@@ -0,0 +1,34 @@
|
||||
# =============================================================================
|
||||
# Service « vitrine-pixels » : lance lecteur.py à chaque démarrage du Pi
|
||||
# =============================================================================
|
||||
#
|
||||
# Un « service » est un programme que Linux démarre tout seul, relance s'il
|
||||
# plante, et dont il garde le journal. Ce fichier est un MODÈLE :
|
||||
# installation/installer.sh remplace @PROJET@ et @PYTHON@ par les vrais
|
||||
# chemins et le copie dans /etc/systemd/system/.
|
||||
#
|
||||
# Commandes utiles (voir MODE_VITRINE.md) :
|
||||
# sudo systemctl enable --now vitrine-pixels activer + démarrer
|
||||
# sudo systemctl disable --now vitrine-pixels désactiver + arrêter
|
||||
# sudo systemctl restart vitrine-pixels relancer (après un réglage)
|
||||
# systemctl status vitrine-pixels est-ce que ça tourne ?
|
||||
# journalctl -u vitrine-pixels -f voir ce que le lecteur affiche
|
||||
|
||||
[Unit]
|
||||
Description=Vitrine pixels : lecteur d'animations sur la matrice LED
|
||||
After=local-fs.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
ExecStart="@PYTHON@" "@PROJET@/lecteur.py"
|
||||
WorkingDirectory=@PROJET@
|
||||
# Le lecteur a besoin des droits d'administrateur pour parler aux LED.
|
||||
User=root
|
||||
# S'il s'arrête (erreur, fichier abîmé…), on le relance au bout de 5 s.
|
||||
Restart=always
|
||||
RestartSec=5
|
||||
# Afficher les messages tout de suite dans le journal.
|
||||
Environment=PYTHONUNBUFFERED=1
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@@ -0,0 +1,41 @@
|
||||
GIMP Palette
|
||||
Name: Vitrine pixels (LED)
|
||||
Columns: 8
|
||||
#
|
||||
255 0 0 Rouge
|
||||
255 90 0 Orange
|
||||
255 200 0 Jaune
|
||||
0 255 0 Vert
|
||||
0 255 200 Cyan
|
||||
0 60 255 Bleu
|
||||
140 0 255 Violet
|
||||
255 0 110 Rose
|
||||
153 0 0 Rouge moyen
|
||||
153 54 0 Orange moyen
|
||||
153 120 0 Jaune moyen
|
||||
0 153 0 Vert moyen
|
||||
0 153 120 Cyan moyen
|
||||
0 36 153 Bleu moyen
|
||||
84 0 153 Violet moyen
|
||||
153 0 66 Rose moyen
|
||||
89 0 0 Rouge sombre
|
||||
89 31 0 Orange sombre
|
||||
89 70 0 Jaune sombre
|
||||
0 89 0 Vert sombre
|
||||
0 89 70 Cyan sombre
|
||||
0 21 89 Bleu sombre
|
||||
49 0 89 Violet sombre
|
||||
89 0 38 Rose sombre
|
||||
255 140 140 Rouge pastel
|
||||
255 181 140 Orange pastel
|
||||
255 230 140 Jaune pastel
|
||||
140 255 140 Vert pastel
|
||||
140 255 230 Cyan pastel
|
||||
140 167 255 Bleu pastel
|
||||
203 140 255 Violet pastel
|
||||
255 140 190 Rose pastel
|
||||
0 0 0 Noir (éteint)
|
||||
90 90 90 Gris sombre
|
||||
160 160 160 Gris
|
||||
255 200 140 Blanc chaud
|
||||
255 255 255 Blanc
|
||||
@@ -0,0 +1,344 @@
|
||||
"""
|
||||
=============================================================================
|
||||
LECTEUR D'ANIMATIONS — de Krita à la matrice
|
||||
Workshop « Vitrine pixels »
|
||||
=============================================================================
|
||||
|
||||
Ce programme joue EN BOUCLE toutes les animations d'un dossier :
|
||||
|
||||
- des GIF animés (coeur.gif)
|
||||
- des PNG animés (APNG) (vague.png)
|
||||
- des WebP animés (pluie.webp)
|
||||
- des images fixes (logo.png : affichée quelques secondes)
|
||||
- des SÉQUENCES d'images (un sous-dossier rempli de PNG numérotés :
|
||||
vague_0000.png, vague_0001.png…, comme
|
||||
Krita les exporte)
|
||||
|
||||
Elles sont jouées par ordre alphabétique : pour choisir l'ordre,
|
||||
commencez les noms par un numéro (01_coeur.gif, 02_vague/…).
|
||||
|
||||
Le dossier est relu à chaque tour : on peut ajouter ou retirer des
|
||||
animations sans arrêter le programme.
|
||||
|
||||
|
||||
COMMENT LE LANCER
|
||||
-----------------
|
||||
Sur le Raspberry Pi (sudo est obligatoire pour parler à la matrice) :
|
||||
sudo ~/vitrine-env/bin/python lecteur.py
|
||||
|
||||
Sur votre ordinateur, avec le simulateur :
|
||||
python3 lecteur.py --simulateur
|
||||
|
||||
Pour essayer UN SEUL fichier ou dossier :
|
||||
python3 lecteur.py --simulateur mon_animation.gif
|
||||
|
||||
Pour lire un autre dossier :
|
||||
python3 lecteur.py --dossier /chemin/vers/mes/animations
|
||||
|
||||
|
||||
ORGANISATION DU PROGRAMME
|
||||
-------------------------
|
||||
1. Réglages.
|
||||
2. Trouver les animations dans le dossier.
|
||||
3. Charger une animation : la découper en images de 32 × 8.
|
||||
4. Jouer une animation.
|
||||
5. Les horaires (allumer la vitrine seulement à certaines heures).
|
||||
6. Le programme principal.
|
||||
"""
|
||||
|
||||
import argparse # pour lire les options (--simulateur, --dossier…)
|
||||
import datetime # pour connaître l'heure (horaires de la vitrine)
|
||||
import os # pour parcourir les dossiers et les fichiers
|
||||
import time
|
||||
|
||||
from PIL import Image, ImageSequence
|
||||
|
||||
import matrice # notre moteur (fichier matrice.py, dans le même dossier)
|
||||
from matrice import LARGEUR, HAUTEUR
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# 1. RÉGLAGES
|
||||
# =============================================================================
|
||||
|
||||
# Où chercher les animations ? On essaie ces dossiers dans l'ordre et on
|
||||
# prend le premier qui existe.
|
||||
# - /boot/firmware/animations : sur la carte SD du Pi, dans la partie
|
||||
# qu'on peut ouvrir depuis N'IMPORTE QUEL ordinateur (Windows, Mac,
|
||||
# Linux). Voir MODE_VITRINE.md.
|
||||
# - /boot/animations : la même chose sur les systèmes plus anciens
|
||||
# (Raspbian 11 « Bullseye » et avant).
|
||||
# - le dossier « animations » à côté de ce programme.
|
||||
ICI = os.path.dirname(os.path.abspath(__file__))
|
||||
DOSSIERS_POSSIBLES = [
|
||||
"/boot/firmware/animations",
|
||||
"/boot/animations",
|
||||
os.path.join(ICI, "animations"),
|
||||
]
|
||||
|
||||
# Vitesse des SÉQUENCES d'images (sous-dossiers de PNG), en images par
|
||||
# seconde, si le dossier ne contient pas de fichier « images_par_seconde.txt ».
|
||||
# Les GIF, APNG et WebP, eux, contiennent leur propre vitesse : Krita
|
||||
# l'enregistre à l'export.
|
||||
IMAGES_PAR_SECONDE = 12
|
||||
|
||||
# Durée minimum d'une animation, en secondes. Une animation courte
|
||||
# (un cœur qui bat en 1 s) est rejouée jusqu'à atteindre cette durée.
|
||||
# Une image fixe reste affichée pendant cette durée.
|
||||
DUREE_MINIMUM = 10
|
||||
|
||||
# Noir entre deux animations, en secondes (0 = enchaîner directement).
|
||||
PAUSE_ENTRE_ANIMATIONS = 0.3
|
||||
|
||||
# HORAIRES de la vitrine : la matrice ne s'allume qu'entre ces deux heures.
|
||||
# None = toujours allumée. Exemple : HORAIRES = ("09:00", "22:30")
|
||||
HORAIRES = None
|
||||
|
||||
# Extensions de fichiers reconnues (en minuscules).
|
||||
EXTENSIONS = (".gif", ".png", ".apng", ".webp", ".jpg", ".jpeg", ".bmp")
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# 2. TROUVER LES ANIMATIONS
|
||||
# =============================================================================
|
||||
|
||||
def trouver_dossier():
|
||||
"""Renvoie le premier dossier d'animations qui existe."""
|
||||
for dossier in DOSSIERS_POSSIBLES:
|
||||
if os.path.isdir(dossier):
|
||||
return dossier
|
||||
return DOSSIERS_POSSIBLES[-1]
|
||||
|
||||
|
||||
def est_cache(nom):
|
||||
"""Les fichiers qui commencent par un point sont « cachés ».
|
||||
Un Mac en crée sur les clés USB et cartes SD (._coeur.gif, .DS_Store) :
|
||||
ce ne sont pas des images, il faut les ignorer."""
|
||||
return nom.startswith(".")
|
||||
|
||||
|
||||
def images_du_dossier(dossier):
|
||||
"""Liste triée des images contenues dans un dossier (pour une séquence)."""
|
||||
return sorted(
|
||||
os.path.join(dossier, nom) for nom in os.listdir(dossier)
|
||||
if not est_cache(nom) and nom.lower().endswith(EXTENSIONS)
|
||||
)
|
||||
|
||||
|
||||
def lister_animations(dossier):
|
||||
"""Liste, par ordre alphabétique, ce qu'il y a à jouer dans le dossier :
|
||||
les fichiers images, et les sous-dossiers qui contiennent des images."""
|
||||
if not os.path.isdir(dossier):
|
||||
return []
|
||||
resultat = []
|
||||
for nom in sorted(os.listdir(dossier), key=str.lower):
|
||||
if est_cache(nom):
|
||||
continue
|
||||
chemin = os.path.join(dossier, nom)
|
||||
if os.path.isdir(chemin):
|
||||
if images_du_dossier(chemin):
|
||||
resultat.append(chemin)
|
||||
elif nom.lower().endswith(EXTENSIONS):
|
||||
resultat.append(chemin)
|
||||
return resultat
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# 3. CHARGER UNE ANIMATION
|
||||
# =============================================================================
|
||||
#
|
||||
# Une animation, pour nous, c'est une LISTE d'étapes. Chaque étape est un
|
||||
# couple (image de 32 × 8, durée en secondes).
|
||||
|
||||
def adapter_image(image, nom):
|
||||
"""Met n'importe quelle image au format de la matrice : 32 × 8, RVB."""
|
||||
|
||||
# 1) La transparence devient du noir (= LED éteinte).
|
||||
image = image.convert("RGBA")
|
||||
fond = Image.new("RGBA", image.size, (0, 0, 0, 255))
|
||||
image = Image.alpha_composite(fond, image).convert("RGB")
|
||||
|
||||
largeur, hauteur = image.size
|
||||
if (largeur, hauteur) == (LARGEUR, HAUTEUR):
|
||||
return image # parfait, rien à faire
|
||||
|
||||
# 2) L'image est un AGRANDISSEMENT exact (320 × 80 = 32 × 8 fois 10) :
|
||||
# on la réduit avec « le plus proche voisin », qui garde des pixels
|
||||
# nets sans inventer de couleurs intermédiaires.
|
||||
if largeur % LARGEUR == 0 and hauteur % HAUTEUR == 0 \
|
||||
and largeur // LARGEUR == hauteur // HAUTEUR:
|
||||
return image.resize((LARGEUR, HAUTEUR), PLUS_PROCHE_VOISIN)
|
||||
|
||||
# 3) Autre taille (par exemple 39 × 7, le format final) : on la pose
|
||||
# au centre, sans la déformer. Ce qui dépasse est coupé.
|
||||
avertir(nom, f"taille {largeur} × {hauteur} au lieu de {LARGEUR} × {HAUTEUR} : "
|
||||
"image centrée (ce qui dépasse est coupé)")
|
||||
toile = Image.new("RGB", (LARGEUR, HAUTEUR))
|
||||
toile.paste(image, ((LARGEUR - largeur) // 2, (HAUTEUR - hauteur) // 2))
|
||||
return toile
|
||||
|
||||
|
||||
# Le nom du filtre « plus proche voisin » a changé avec Pillow 9.1 :
|
||||
# on prend le nouveau s'il existe, sinon l'ancien (Raspbian 11 a Pillow 8).
|
||||
PLUS_PROCHE_VOISIN = getattr(Image, "Resampling", Image).NEAREST
|
||||
|
||||
_deja_averti = set()
|
||||
|
||||
|
||||
def avertir(nom, message):
|
||||
"""Affiche un avertissement, une seule fois par fichier et par message."""
|
||||
if (nom, message) not in _deja_averti:
|
||||
_deja_averti.add((nom, message))
|
||||
print(f" ⚠ {os.path.basename(nom)} : {message}")
|
||||
|
||||
|
||||
def lire_vitesse(dossier):
|
||||
"""Lit « images_par_seconde.txt » dans le dossier d'une séquence, s'il existe."""
|
||||
fichier = os.path.join(dossier, "images_par_seconde.txt")
|
||||
try:
|
||||
with open(fichier) as f:
|
||||
return float(f.read().strip().replace(",", "."))
|
||||
except (OSError, ValueError):
|
||||
return IMAGES_PAR_SECONDE
|
||||
|
||||
|
||||
def charger_fichier(chemin):
|
||||
"""Découpe un fichier image (fixe ou animé) en liste d'étapes."""
|
||||
etapes = []
|
||||
with Image.open(chemin) as fichier:
|
||||
# ImageSequence.Iterator parcourt les images d'un fichier animé.
|
||||
# Pour une image fixe, il n'y en a qu'une.
|
||||
for image in ImageSequence.Iterator(fichier):
|
||||
# La durée de chaque image est rangée dans le fichier, en ms.
|
||||
# Certains GIF indiquent 0 : on prend alors 100 ms.
|
||||
duree_ms = image.info.get("duration") or 100
|
||||
etapes.append((adapter_image(image, chemin), duree_ms / 1000))
|
||||
if len(etapes) == 1:
|
||||
# Image fixe : on l'affiche pendant toute la durée minimum.
|
||||
etapes = [(etapes[0][0], DUREE_MINIMUM)]
|
||||
return etapes
|
||||
|
||||
|
||||
def charger_sequence(dossier):
|
||||
"""Charge un dossier de PNG numérotés comme une animation."""
|
||||
duree = 1 / lire_vitesse(dossier)
|
||||
etapes = []
|
||||
for chemin in images_du_dossier(dossier):
|
||||
with Image.open(chemin) as image:
|
||||
etapes.append((adapter_image(image, dossier), duree))
|
||||
return etapes
|
||||
|
||||
|
||||
def charger(chemin):
|
||||
"""Charge une animation, que ce soit un fichier ou un dossier."""
|
||||
if os.path.isdir(chemin):
|
||||
return charger_sequence(chemin)
|
||||
return charger_fichier(chemin)
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# 4. JOUER UNE ANIMATION
|
||||
# =============================================================================
|
||||
|
||||
def jouer(etapes, duree_minimum=DUREE_MINIMUM):
|
||||
"""Joue la liste d'étapes, et la rejoue jusqu'à atteindre la durée minimum."""
|
||||
debut = time.monotonic()
|
||||
while True:
|
||||
for image, duree in etapes:
|
||||
moment = time.monotonic()
|
||||
matrice.montrer(image)
|
||||
# On retire le temps déjà passé à afficher, pour garder le rythme.
|
||||
matrice.attendre((duree - (time.monotonic() - moment)) * 1000)
|
||||
if time.monotonic() - debut >= duree_minimum:
|
||||
break
|
||||
|
||||
|
||||
def ecran_d_attente(dossier):
|
||||
"""Quand il n'y a rien à jouer : un petit point qui respire en bas à droite."""
|
||||
print(f"Aucune animation dans {dossier} : en attente…")
|
||||
for i in range(50):
|
||||
matrice.effacer()
|
||||
lumiere = int(60 + 60 * abs(25 - i) / 25)
|
||||
matrice.pixel(LARGEUR - 1, HAUTEUR - 1, (0, lumiere, lumiere))
|
||||
matrice.montrer()
|
||||
matrice.attendre(100)
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# 5. LES HORAIRES
|
||||
# =============================================================================
|
||||
|
||||
def en_horaires():
|
||||
"""Vrai si la vitrine doit être allumée à cette heure-ci."""
|
||||
if HORAIRES is None:
|
||||
return True
|
||||
maintenant = datetime.datetime.now().strftime("%H:%M") # ex. "14:05"
|
||||
debut, fin = HORAIRES
|
||||
if debut <= fin:
|
||||
return debut <= maintenant < fin # ex. de 09:00 à 22:30
|
||||
return maintenant >= debut or maintenant < fin # ex. de 18:00 à 02:00 (passe minuit)
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# 6. LE PROGRAMME PRINCIPAL
|
||||
# =============================================================================
|
||||
|
||||
def main():
|
||||
options = argparse.ArgumentParser(description="Joue les animations sur la matrice 8 × 32.")
|
||||
options.add_argument("chemin", nargs="?",
|
||||
help="un seul fichier ou dossier à jouer (facultatif)")
|
||||
options.add_argument("--dossier", help="dossier d'animations à lire")
|
||||
options.add_argument("--simulateur", action="store_true",
|
||||
help="afficher dans une fenêtre au lieu de la matrice")
|
||||
args = options.parse_args()
|
||||
|
||||
matrice.demarrer(simulateur=args.simulateur)
|
||||
dossier = args.dossier or trouver_dossier()
|
||||
|
||||
try:
|
||||
while True: # boucle infinie : la vitrine tourne jusqu'à ce qu'on l'arrête
|
||||
if not en_horaires():
|
||||
matrice.eteindre()
|
||||
matrice.attendre(30_000) # on revérifie l'heure toutes les 30 s
|
||||
continue
|
||||
|
||||
# Un seul fichier demandé, ou tout le dossier ?
|
||||
playlist = [args.chemin] if args.chemin else lister_animations(dossier)
|
||||
if not playlist:
|
||||
ecran_d_attente(dossier)
|
||||
continue
|
||||
|
||||
for chemin in playlist:
|
||||
print("▶", os.path.relpath(chemin, dossier) if not args.chemin else chemin)
|
||||
try:
|
||||
etapes = charger(chemin)
|
||||
except Exception as erreur:
|
||||
# Un fichier abîmé ne doit pas arrêter la vitrine : on le saute.
|
||||
print(f" ✗ impossible de lire ce fichier ({erreur}) : on passe au suivant")
|
||||
continue
|
||||
if etapes:
|
||||
jouer(etapes)
|
||||
if PAUSE_ENTRE_ANIMATIONS > 0:
|
||||
matrice.effacer()
|
||||
matrice.montrer()
|
||||
matrice.attendre(PAUSE_ENTRE_ANIMATIONS * 1000)
|
||||
if not en_horaires():
|
||||
break
|
||||
|
||||
except KeyboardInterrupt:
|
||||
# Ctrl + C dans le terminal : on sort proprement.
|
||||
print("\nArrêt demandé.")
|
||||
finally:
|
||||
# « finally » s'exécute toujours, même en cas d'erreur :
|
||||
# on ne laisse jamais la matrice allumée en partant.
|
||||
try:
|
||||
matrice.eteindre()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
# Cette ligne veut dire : « si on lance CE fichier directement, exécuter
|
||||
# main() ». Quand un autre programme l'importe (outils/verifier_animation.py),
|
||||
# main() n'est pas lancé : il peut juste réutiliser ses fonctions.
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,502 @@
|
||||
"""
|
||||
=============================================================================
|
||||
MATRICE LED WS2812B 8 x 32 (256 pixels) — le « moteur » pour Raspberry Pi
|
||||
Workshop « Vitrine pixels »
|
||||
=============================================================================
|
||||
|
||||
Ce fichier est la SALLE DES MACHINES. Vous n'avez normalement pas besoin
|
||||
de le modifier, sauf les RÉGLAGES de la partie 1. Les autres programmes
|
||||
du dossier s'en servent :
|
||||
|
||||
- lecteur.py joue les animations dessinées dans Krita (GIF, PNG…) ;
|
||||
- test_matrice.py vérifie le branchement et l'orientation ;
|
||||
- mes_animations.py animations écrites en code (comme sur l'Arduino).
|
||||
|
||||
|
||||
CE QUI CHANGE PAR RAPPORT À L'ARDUINO
|
||||
-------------------------------------
|
||||
Le principe électrique est EXACTEMENT le même que dans le dossier
|
||||
matrice_8x32 : les 256 LED forment une chaîne, et on leur envoie une
|
||||
longue liste de couleurs par UN SEUL FIL de données.
|
||||
|
||||
Ce qui change, c'est le « cerveau » :
|
||||
- l'Arduino est un petit microcontrôleur : il ne fait qu'une chose,
|
||||
le programme qu'on lui a téléversé ;
|
||||
- le Raspberry Pi est un vrai petit ORDINATEUR (avec Linux, des
|
||||
fichiers, le réseau…). Il peut donc LIRE DES IMAGES : on n'a plus
|
||||
besoin de traduire chaque dessin en code.
|
||||
|
||||
Le langage aussi change : Python au lieu du C++ de l'Arduino.
|
||||
Python se lit presque comme de l'anglais, et il n'y a pas besoin de
|
||||
« téléverser » : on lance le programme directement.
|
||||
|
||||
|
||||
DEUX ÉCRANS POSSIBLES
|
||||
---------------------
|
||||
Ce moteur sait afficher l'image :
|
||||
1. sur la VRAIE matrice, si on est sur un Raspberry Pi avec la
|
||||
bibliothèque rpi_ws281x installée ;
|
||||
2. dans un SIMULATEUR : une fenêtre sur votre ordinateur, avec 256
|
||||
ronds de couleur. Pratique pour préparer ses animations chez soi,
|
||||
sans matériel.
|
||||
Le choix se fait tout seul (ou avec l'option --simulateur).
|
||||
Il existe aussi un 3e « écran », l'ENREGISTREUR : avec l'option
|
||||
--enregistrer fichier.gif, les images sont enregistrées dans un GIF animé.
|
||||
|
||||
|
||||
ORGANISATION DU FICHIER
|
||||
-----------------------
|
||||
1. Réglages : taille, broche, luminosité, limite de courant, orientation.
|
||||
2. Les couleurs (avec leur nom en français).
|
||||
3. La fonction XY() : de la position (x, y) au numéro de LED.
|
||||
4. La correction gamma et la limite de courant.
|
||||
5. Les écrans : la vraie matrice, le simulateur et l'enregistreur.
|
||||
6. Les outils de dessin : pixel(), effacer(), montrer(), attendre()…
|
||||
"""
|
||||
|
||||
import colorsys # conversions de couleurs (teinte → rouge, vert, bleu)
|
||||
import sys # pour lire les options de la ligne de commande
|
||||
import time # pour mesurer et attendre le temps qui passe
|
||||
|
||||
# Pillow est la bibliothèque Python qui sait lire et manipuler les images.
|
||||
# Si elle manque : sudo apt install python3-pil (sur le Pi)
|
||||
# pip install pillow (sur un ordinateur)
|
||||
from PIL import Image
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# 1. RÉGLAGES
|
||||
# =============================================================================
|
||||
|
||||
# Taille de la matrice : 32 colonnes (largeur) × 8 lignes (hauteur).
|
||||
LARGEUR = 32
|
||||
HAUTEUR = 8
|
||||
NB_LEDS = LARGEUR * HAUTEUR # = 256
|
||||
|
||||
# Broche du Raspberry Pi reliée (à travers l'adaptateur 74AHCT125) au fil
|
||||
# VERT (DIN) de la matrice. Ce numéro est le numéro « GPIO », pas le numéro
|
||||
# de la patte : GPIO 18 = patte physique n° 12 du connecteur du Pi.
|
||||
# Ne changez pas sans raison : seules quelques broches savent produire
|
||||
# ce signal très rapide (GPIO 18 est la plus simple).
|
||||
BROCHE_GPIO = 18
|
||||
|
||||
# SIGNAL INVERSÉ : à mettre à True UNIQUEMENT si le signal passe par un
|
||||
# transistor (montage « sans 74AHCT125 », solution B du README). Un transistor
|
||||
# inverse le signal (un 1 devient un 0) : la bibliothèque l'inverse alors
|
||||
# une seconde fois avant de l'envoyer, et tout redevient juste.
|
||||
INVERSER_SIGNAL = False
|
||||
|
||||
# LUMINOSITÉ générale, de 0 (éteint) à 255 (maximum).
|
||||
# Ces LED sont TRÈS lumineuses : 40 suffit en intérieur.
|
||||
# En vitrine, en plein jour, on pourra monter vers 100 ou plus,
|
||||
# à condition d'avoir une alimentation assez puissante.
|
||||
LUMINOSITE = 40
|
||||
|
||||
# SÉCURITÉ : courant maximum autorisé, en milliampères (1000 mA = 1 A).
|
||||
# Une LED en blanc à fond consomme environ 60 mA (20 mA par couleur).
|
||||
# 256 LED × 60 mA = plus de 15 A ! L'alimentation en fournit beaucoup moins.
|
||||
# Avant chaque image, le moteur CALCULE ce que les LED vont consommer et,
|
||||
# si ça dépasse cette limite, il baisse la luminosité de cette image.
|
||||
# → Réglez cette valeur UN PEU EN DESSOUS de la limite de l'alimentation
|
||||
# (ex. : alimentation d'établi limitée à 3 A → 2500 mA ici).
|
||||
COURANT_MAX_MA = 2500
|
||||
|
||||
# ORIENTATION de la matrice (voir test_matrice.py).
|
||||
# Le pixel ROUGE du test doit être EN HAUT À GAUCHE, le VERT EN HAUT À DROITE.
|
||||
# - rouge et vert inversés (gauche/droite) → INVERSER_X = True
|
||||
# - rouge et vert en bas au lieu d'en haut → INVERSER_Y = True
|
||||
INVERSER_X = False
|
||||
INVERSER_Y = False
|
||||
|
||||
# CORRECTION GAMMA (voir partie 4). 2.2 = comme un écran d'ordinateur.
|
||||
# 1.0 = pas de correction (les couleurs sombres paraîtront trop claires).
|
||||
GAMMA = 2.2
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# 2. LES COULEURS
|
||||
# =============================================================================
|
||||
#
|
||||
# En Python, une couleur est un « tuple » de trois nombres entre
|
||||
# parenthèses : (rouge, vert, bleu), chacun de 0 à 255.
|
||||
# (255, 0, 0) = rouge vif
|
||||
# (255, 128, 0) = orange
|
||||
# (20, 0, 40) = violet sombre
|
||||
#
|
||||
# On donne un nom aux plus courantes. Les noms en MAJUSCULES sont, par
|
||||
# convention, des valeurs qui ne changent pas (des « constantes »).
|
||||
|
||||
NOIR = (0, 0, 0)
|
||||
BLANC = (255, 255, 255)
|
||||
ROUGE = (255, 0, 0)
|
||||
ORANGE = (255, 100, 0)
|
||||
JAUNE = (255, 200, 0)
|
||||
VERT = (0, 255, 0)
|
||||
CYAN = (0, 255, 255)
|
||||
BLEU = (0, 0, 255)
|
||||
VIOLET = (130, 0, 255)
|
||||
MAGENTA = (255, 0, 255)
|
||||
ROSE = (255, 20, 100)
|
||||
|
||||
|
||||
def teinte(t, saturation=255, valeur=255):
|
||||
"""Fabrique une couleur à partir d'une TEINTE, comme CHSV dans FastLED.
|
||||
|
||||
t (la teinte) va de 0 à 255 et fait le tour de l'arc-en-ciel :
|
||||
0 rouge, 32 orange, 64 jaune, 96 vert, 128 cyan, 160 bleu,
|
||||
192 violet, 224 rose… puis retour au rouge (256 = 0).
|
||||
saturation : 0 = gris/blanc, 255 = couleur pure.
|
||||
valeur : 0 = noir, 255 = pleine lumière.
|
||||
"""
|
||||
# colorsys travaille avec des nombres entre 0 et 1 : on divise par 255,
|
||||
# puis on remultiplie le résultat par 255.
|
||||
# « % 256 » = reste de la division par 256 : 300 devient 44, la teinte boucle.
|
||||
r, v, b = colorsys.hsv_to_rgb((t % 256) / 256, saturation / 255, valeur / 255)
|
||||
return (round(r * 255), round(v * 255), round(b * 255))
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# 3. DE LA POSITION (x, y) AU NUMÉRO DE LED
|
||||
# =============================================================================
|
||||
#
|
||||
# Pour dessiner, on pense en coordonnées :
|
||||
# x = colonne, de 0 (à gauche) à 31 (à droite)
|
||||
# y = ligne, de 0 (en haut) à 7 (en bas)
|
||||
# Le point (0, 0) est en haut à gauche, comme dans Krita.
|
||||
#
|
||||
# Mais les LED sont numérotées dans l'ordre de la chaîne, qui fait des
|
||||
# zigzags (« serpentin ») : elle descend la 1re colonne, remonte la 2e…
|
||||
#
|
||||
# x=0 x=1 x=2 x=3
|
||||
# y=0 0 15 16 31 …
|
||||
# y=1 1 14 17 30
|
||||
# … … … … …
|
||||
# y=7 7 → 8 23 → 24
|
||||
#
|
||||
# C'est la même fonction que dans matrice_8x32.ino, traduite en Python.
|
||||
|
||||
def XY(x, y):
|
||||
"""Renvoie le numéro (0 à 255) de la LED placée en colonne x, ligne y."""
|
||||
if INVERSER_X:
|
||||
x = (LARGEUR - 1) - x
|
||||
if INVERSER_Y:
|
||||
y = (HAUTEUR - 1) - y
|
||||
|
||||
if x % 2 == 0:
|
||||
# Colonne paire : la chaîne DESCEND.
|
||||
return x * HAUTEUR + y
|
||||
else:
|
||||
# Colonne impaire : la chaîne REMONTE.
|
||||
return x * HAUTEUR + (HAUTEUR - 1 - y)
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# 4. CORRECTION GAMMA ET LIMITE DE COURANT
|
||||
# =============================================================================
|
||||
#
|
||||
# POURQUOI UNE « CORRECTION GAMMA » ?
|
||||
# Notre œil n'est pas une règle graduée : il est beaucoup plus sensible aux
|
||||
# différences dans les sombres que dans les clairs. Les écrans d'ordinateur
|
||||
# en tiennent compte : dans Krita, un gris à 128 (la moitié de 255) ne
|
||||
# produit en réalité qu'environ 22 % de la lumière du blanc, et c'est ce
|
||||
# que notre œil perçoit comme « gris moyen ».
|
||||
#
|
||||
# Les LED, elles, sont « honnêtes » : 128 donne VRAIMENT 50 % de lumière,
|
||||
# ce que l'œil voit comme presque blanc. Sans correction, les dégradés
|
||||
# dessinés dans Krita paraîtraient délavés sur la matrice.
|
||||
#
|
||||
# On prépare donc une TABLE de 256 cases : pour chaque valeur de 0 à 255
|
||||
# venue de l'image, la valeur à envoyer à la LED pour que ça « ressemble »
|
||||
# à l'écran. Calculée une seule fois, au démarrage.
|
||||
#
|
||||
# Conséquence à connaître : les couleurs très sombres de l'image deviennent
|
||||
# 0 (éteint) sur la matrice. Voir le GUIDE_KRITA.md.
|
||||
|
||||
TABLE_GAMMA = [round(255 * (i / 255) ** GAMMA) for i in range(256)]
|
||||
# (Cette ligne se lit : « pour chaque i de 0 à 255, calculer 255 × (i/255)^gamma ».
|
||||
# Les crochets fabriquent une liste : c'est une « liste en compréhension ».)
|
||||
|
||||
# Consommation approximative d'une LED, pour la limite de courant.
|
||||
MA_PAR_COULEUR = 20 # une couleur (R, G ou B) à fond ≈ 20 mA
|
||||
MA_AU_REPOS = 1 # la petite puce de chaque LED consomme ≈ 1 mA même éteinte
|
||||
|
||||
|
||||
def pixels_de(image):
|
||||
"""Liste des couleurs (r, v, b) de tous les pixels d'une image RVB,
|
||||
ligne par ligne : (0,0), (1,0), (2,0)… puis la ligne suivante.
|
||||
(tobytes() donne les octets r, v, b, r, v, b… : on les regroupe par 3.)"""
|
||||
octets = image.tobytes()
|
||||
return [tuple(octets[i:i + 3]) for i in range(0, len(octets), 3)]
|
||||
|
||||
|
||||
def preparer_valeurs(image):
|
||||
"""Transforme une image Pillow en liste de 256 couleurs prêtes à envoyer.
|
||||
|
||||
Étapes : correction gamma → luminosité → limite de courant.
|
||||
Renvoie une liste de tuples (r, v, b), dans l'ordre des lignes de l'image
|
||||
(pixel (0,0), (1,0), (2,0)… puis la ligne suivante).
|
||||
"""
|
||||
valeurs = []
|
||||
for (r, v, b) in pixels_de(image): # tous les pixels, un par un
|
||||
valeurs.append((
|
||||
TABLE_GAMMA[r] * LUMINOSITE // 255, # « // » = division entière
|
||||
TABLE_GAMMA[v] * LUMINOSITE // 255,
|
||||
TABLE_GAMMA[b] * LUMINOSITE // 255,
|
||||
))
|
||||
|
||||
# Estimation du courant : on additionne toutes les composantes.
|
||||
total = sum(r + v + b for (r, v, b) in valeurs)
|
||||
repos = NB_LEDS * MA_AU_REPOS
|
||||
courant = repos + total * MA_PAR_COULEUR / 255
|
||||
|
||||
if courant > COURANT_MAX_MA:
|
||||
# Trop gourmand : on baisse toute l'image dans la bonne proportion.
|
||||
facteur = (COURANT_MAX_MA - repos) / (courant - repos)
|
||||
valeurs = [(int(r * facteur), int(v * facteur), int(b * facteur))
|
||||
for (r, v, b) in valeurs]
|
||||
return valeurs
|
||||
|
||||
|
||||
def courant_estime_ma(image):
|
||||
"""Courant (en mA) que consommerait cette image, limite comprise."""
|
||||
valeurs = preparer_valeurs(image)
|
||||
total = sum(r + v + b for (r, v, b) in valeurs)
|
||||
return NB_LEDS * MA_AU_REPOS + total * MA_PAR_COULEUR / 255
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# 5. LES ÉCRANS
|
||||
# =============================================================================
|
||||
#
|
||||
# Une « classe » est un modèle d'objet : elle regroupe des données et les
|
||||
# fonctions (on dit « méthodes ») qui s'en servent. Nos deux écrans ont les
|
||||
# mêmes méthodes : afficher(image), attendre(secondes) et eteindre().
|
||||
# Le reste du programme peut donc utiliser l'un ou l'autre sans changer
|
||||
# une ligne : c'est tout l'intérêt.
|
||||
|
||||
class MatriceReelle:
|
||||
"""La vraie matrice, branchée sur le Raspberry Pi."""
|
||||
|
||||
def __init__(self):
|
||||
# On importe la bibliothèque ICI, et pas en haut du fichier :
|
||||
# comme ça, le simulateur fonctionne sur un ordinateur où elle
|
||||
# n'est pas installée.
|
||||
from rpi_ws281x import PixelStrip, Color, ws
|
||||
self.Color = Color
|
||||
self.bande = PixelStrip(
|
||||
NB_LEDS, # nombre de LED
|
||||
BROCHE_GPIO, # broche des données (GPIO 18)
|
||||
freq_hz=800_000, # 800 000 bits par seconde, comme FastLED
|
||||
dma=10, # canal interne utilisé pour envoyer les bits
|
||||
invert=INVERSER_SIGNAL, # True seulement avec le montage à transistor
|
||||
brightness=255, # on gère la luminosité nous-mêmes (partie 4)
|
||||
channel=0,
|
||||
strip_type=ws.WS2811_STRIP_GRB, # ordre des couleurs : Vert, Rouge, Bleu
|
||||
)
|
||||
self.bande.begin() # démarre l'envoi (il faut être administrateur : sudo)
|
||||
|
||||
def afficher(self, image):
|
||||
valeurs = preparer_valeurs(image)
|
||||
for y in range(HAUTEUR):
|
||||
for x in range(LARGEUR):
|
||||
r, v, b = valeurs[y * LARGEUR + x]
|
||||
self.bande.setPixelColor(XY(x, y), self.Color(r, v, b))
|
||||
self.bande.show() # envoie les 256 couleurs dans le fil (≈ 8 ms)
|
||||
|
||||
def attendre(self, secondes):
|
||||
if secondes > 0:
|
||||
time.sleep(secondes)
|
||||
|
||||
def eteindre(self):
|
||||
for i in range(NB_LEDS):
|
||||
self.bande.setPixelColor(i, 0)
|
||||
self.bande.show()
|
||||
|
||||
|
||||
class Simulateur:
|
||||
"""Une fenêtre sur l'ordinateur qui imite la matrice (256 ronds)."""
|
||||
|
||||
TAILLE = 26 # taille d'un pixel à l'écran, en points
|
||||
MARGE = 4 # espace entre deux pixels
|
||||
|
||||
def __init__(self, titre="Simulateur — matrice 8 × 32"):
|
||||
# tkinter est la bibliothèque de fenêtres livrée avec Python.
|
||||
import tkinter
|
||||
self.fenetre = tkinter.Tk()
|
||||
self.fenetre.title(titre)
|
||||
self.fenetre.configure(background="black")
|
||||
pas = self.TAILLE + self.MARGE
|
||||
self.toile = tkinter.Canvas(self.fenetre, width=LARGEUR * pas + self.MARGE,
|
||||
height=HAUTEUR * pas + self.MARGE,
|
||||
background="#111", highlightthickness=0)
|
||||
self.toile.pack(padx=10, pady=10)
|
||||
# Un rond par pixel. On garde leurs numéros pour changer leur couleur.
|
||||
self.ronds = {}
|
||||
for y in range(HAUTEUR):
|
||||
for x in range(LARGEUR):
|
||||
x0 = self.MARGE + x * pas
|
||||
y0 = self.MARGE + y * pas
|
||||
self.ronds[(x, y)] = self.toile.create_oval(
|
||||
x0, y0, x0 + self.TAILLE, y0 + self.TAILLE, fill="#000", outline="#222")
|
||||
# Quand on ferme la fenêtre, on arrête proprement le programme.
|
||||
self.ouverte = True
|
||||
self.fenetre.protocol("WM_DELETE_WINDOW", self._fermer)
|
||||
self.fenetre.update()
|
||||
|
||||
def _fermer(self):
|
||||
self.ouverte = False
|
||||
|
||||
def _rafraichir(self):
|
||||
if not self.ouverte:
|
||||
self.fenetre.destroy()
|
||||
raise SystemExit("Fenêtre du simulateur fermée.")
|
||||
self.fenetre.update()
|
||||
|
||||
def afficher(self, image):
|
||||
# Le simulateur montre les couleurs de l'image telles quelles :
|
||||
# l'écran de l'ordinateur fait déjà sa propre « correction gamma ».
|
||||
for y in range(HAUTEUR):
|
||||
for x in range(LARGEUR):
|
||||
r, v, b = image.getpixel((x, y))
|
||||
self.toile.itemconfig(self.ronds[(x, y)], fill=f"#{r:02x}{v:02x}{b:02x}")
|
||||
self._rafraichir()
|
||||
|
||||
def attendre(self, secondes):
|
||||
# On ne peut pas simplement dormir : la fenêtre se figerait.
|
||||
# On attend par petits bouts en la laissant se redessiner.
|
||||
fin = time.monotonic() + secondes
|
||||
while True:
|
||||
self._rafraichir()
|
||||
reste = fin - time.monotonic()
|
||||
if reste <= 0:
|
||||
break
|
||||
time.sleep(min(reste, 0.01))
|
||||
|
||||
def eteindre(self):
|
||||
if self.ouverte:
|
||||
self.afficher(Image.new("RGB", (LARGEUR, HAUTEUR)))
|
||||
|
||||
|
||||
class Enregistreur:
|
||||
"""Un « écran » qui n'affiche rien : il ENREGISTRE chaque image montrée
|
||||
dans un GIF animé. Une animation écrite en code devient ainsi un fichier,
|
||||
que le lecteur jouera comme un dessin de Krita.
|
||||
Option : --enregistrer animations/10_mon_animation.gif"""
|
||||
|
||||
def __init__(self, fichier):
|
||||
self.fichier = fichier
|
||||
self.images = []
|
||||
self.durees = [] # en secondes, une par image
|
||||
|
||||
def afficher(self, image):
|
||||
self.images.append(image.copy()) # copy() : une photo de la toile à cet instant
|
||||
self.durees.append(0)
|
||||
|
||||
def attendre(self, secondes):
|
||||
# Le temps d'attente devient la durée de l'image affichée juste avant.
|
||||
if self.durees and secondes > 0:
|
||||
self.durees[-1] += secondes
|
||||
|
||||
def eteindre(self):
|
||||
if not self.images:
|
||||
return
|
||||
# Le format GIF ne sait pas afficher une image moins de 20 ms.
|
||||
durees_ms = [max(20, round(d * 1000)) for d in self.durees]
|
||||
self.images[0].save(self.fichier, save_all=True, append_images=self.images[1:],
|
||||
duration=durees_ms, loop=0)
|
||||
print(f"Enregistré : {self.fichier} ({len(self.images)} images, "
|
||||
f"{sum(durees_ms) / 1000:.1f} s)")
|
||||
self.images = []
|
||||
|
||||
|
||||
def ouvrir_ecran(simulateur=None):
|
||||
"""Choisit l'écran : vraie matrice si possible, sinon simulateur.
|
||||
|
||||
simulateur = True → toujours le simulateur ;
|
||||
simulateur = None → décide tout seul (option --simulateur sur la ligne
|
||||
de commande, ou bibliothèque rpi_ws281x absente).
|
||||
Avec l'option « --enregistrer fichier.gif », on enregistre au lieu d'afficher.
|
||||
"""
|
||||
if "--enregistrer" in sys.argv:
|
||||
position = sys.argv.index("--enregistrer")
|
||||
if position + 1 >= len(sys.argv):
|
||||
raise SystemExit("Indiquez un nom de fichier après --enregistrer")
|
||||
print("Écran : enregistrement dans", sys.argv[position + 1])
|
||||
return Enregistreur(sys.argv[position + 1])
|
||||
if simulateur is None:
|
||||
simulateur = "--simulateur" in sys.argv
|
||||
if not simulateur:
|
||||
try:
|
||||
ecran = MatriceReelle()
|
||||
print("Écran : vraie matrice (GPIO %d)." % BROCHE_GPIO)
|
||||
return ecran
|
||||
except ImportError:
|
||||
print("Bibliothèque rpi_ws281x absente : on ouvre le simulateur.")
|
||||
except RuntimeError as erreur:
|
||||
# Erreur typique : programme lancé sans « sudo ».
|
||||
print("Impossible de parler à la matrice :", erreur)
|
||||
print("Sur le Raspberry Pi, lancez le programme avec sudo.")
|
||||
raise SystemExit(1)
|
||||
print("Écran : simulateur.")
|
||||
return Simulateur()
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# 6. LES OUTILS DE DESSIN
|
||||
# =============================================================================
|
||||
#
|
||||
# Les mêmes que sur l'Arduino : on dessine sur une TOILE invisible
|
||||
# (une image Pillow de 32 × 8 pixels), puis on la montre d'un coup.
|
||||
|
||||
toile = Image.new("RGB", (LARGEUR, HAUTEUR)) # l'image en préparation
|
||||
_ecran = None # l'écran, ouvert par demarrer()
|
||||
|
||||
|
||||
def demarrer(simulateur=None):
|
||||
"""À appeler une fois au début du programme : ouvre l'écran."""
|
||||
global _ecran # « global » : on modifie la variable du fichier, pas une copie
|
||||
_ecran = ouvrir_ecran(simulateur)
|
||||
effacer()
|
||||
montrer()
|
||||
|
||||
|
||||
def pixel(x, y, couleur):
|
||||
"""Colore le pixel (x, y). En dehors de la matrice : on ne fait rien."""
|
||||
if 0 <= x < LARGEUR and 0 <= y < HAUTEUR:
|
||||
toile.putpixel((int(x), int(y)), tuple(couleur))
|
||||
|
||||
|
||||
def lire_pixel(x, y):
|
||||
"""Renvoie la couleur actuelle du pixel (x, y) sur la toile."""
|
||||
if 0 <= x < LARGEUR and 0 <= y < HAUTEUR:
|
||||
return toile.getpixel((int(x), int(y)))
|
||||
return NOIR
|
||||
|
||||
|
||||
def effacer(couleur=NOIR):
|
||||
"""Remplit toute la toile d'une couleur (noir par défaut = tout éteindre)."""
|
||||
toile.paste(tuple(couleur), (0, 0, LARGEUR, HAUTEUR))
|
||||
|
||||
|
||||
def assombrir(pourcentage):
|
||||
"""Assombrit toute la toile, comme fadeToBlackBy() de FastLED.
|
||||
assombrir(25) = chaque pixel perd 25 % de sa lumière."""
|
||||
garde = 1 - pourcentage / 100
|
||||
toile.putdata([(int(r * garde), int(v * garde), int(b * garde))
|
||||
for (r, v, b) in pixels_de(toile)])
|
||||
|
||||
|
||||
def montrer(image=None):
|
||||
"""Affiche la toile (ou une autre image de 32 × 8) sur l'écran."""
|
||||
_ecran.afficher(image if image is not None else toile)
|
||||
|
||||
|
||||
def attendre(millisecondes):
|
||||
"""Attend, en millisecondes, comme delay() sur l'Arduino."""
|
||||
_ecran.attendre(millisecondes / 1000)
|
||||
|
||||
|
||||
def eteindre():
|
||||
"""Éteint toute la matrice. À appeler à la fin du programme."""
|
||||
if _ecran is not None:
|
||||
_ecran.eteindre()
|
||||
@@ -0,0 +1,176 @@
|
||||
"""
|
||||
=============================================================================
|
||||
MES ANIMATIONS — dessiner avec du code (en Python)
|
||||
Workshop « Vitrine pixels »
|
||||
=============================================================================
|
||||
|
||||
Krita, c'est pour DESSINER les animations image par image.
|
||||
Ce fichier, c'est pour les CALCULER : hasard, mouvements continus,
|
||||
arc-en-ciel… des choses très longues à dessiner à la main.
|
||||
|
||||
Ce sont les mêmes animations que dans matrice_8x32.ino (Arduino),
|
||||
traduites en Python : comparez les deux fichiers, ligne à ligne !
|
||||
|
||||
Lancer :
|
||||
python3 mes_animations.py --simulateur (sur l'ordinateur)
|
||||
sudo ~/vitrine-env/bin/python mes_animations.py (sur le Pi)
|
||||
|
||||
Transformer la playlist en GIF, pour que le lecteur la joue avec les
|
||||
dessins de l'équipe Krita (la playlist est alors jouée une seule fois) :
|
||||
python3 mes_animations.py --enregistrer animations/10_code.gif
|
||||
|
||||
|
||||
PYTHON EN 6 RÈGLES (pour qui connaît l'Arduino)
|
||||
-----------------------------------------------
|
||||
1. Pas de point-virgule en fin de ligne, pas d'accolades { }.
|
||||
2. Ce sont les ESPACES en début de ligne (l'indentation) qui disent ce
|
||||
qui est « dans » une boucle ou une fonction. 4 espaces par niveau.
|
||||
3. Pas de type à déclarer : x = 3 au lieu de int x = 3;
|
||||
4. Une fonction : def nom(): au lieu de void nom() {
|
||||
5. Une boucle : for x in range(32): (x va de 0 à 31)
|
||||
au lieu de for (int x = 0; x < 32; x++) {
|
||||
6. Un commentaire commence par # au lieu de //
|
||||
|
||||
|
||||
LES OUTILS DE DESSIN (définis dans matrice.py)
|
||||
----------------------------------------------
|
||||
pixel(x, y, couleur) colore un pixel (x de 0 à 31, y de 0 à 7)
|
||||
lire_pixel(x, y) renvoie la couleur d'un pixel
|
||||
effacer() éteint tout (dans la toile)
|
||||
effacer(couleur) remplit tout d'une couleur
|
||||
assombrir(25) assombrit tout de 25 % (traînées)
|
||||
montrer() AFFICHE la toile (sinon rien n'apparaît !)
|
||||
attendre(100) attend 100 millisecondes
|
||||
|
||||
Couleurs : ROUGE, ORANGE, JAUNE, VERT, CYAN, BLEU, VIOLET, MAGENTA, ROSE,
|
||||
BLANC, NOIR, ou (rouge, vert, bleu) de 0 à 255,
|
||||
ou teinte(t) avec t de 0 à 255 pour faire le tour de l'arc-en-ciel.
|
||||
"""
|
||||
|
||||
import random # pour le hasard
|
||||
import sys # pour lire les options de la ligne de commande
|
||||
|
||||
# « from matrice import * » = importer TOUS les outils du moteur, pour
|
||||
# écrire pixel(...) au lieu de matrice.pixel(...).
|
||||
from matrice import *
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# LES ANIMATIONS
|
||||
# =============================================================================
|
||||
# À VOUS DE JOUER : copiez une animation, renommez-la, modifiez-la,
|
||||
# et ajoutez son nom dans la PLAYLIST tout en bas.
|
||||
|
||||
|
||||
# --- Animation 1 : arc-en-ciel qui défile ---
|
||||
def anim_arc_en_ciel():
|
||||
for temps in range(256):
|
||||
for x in range(LARGEUR):
|
||||
for y in range(HAUTEUR):
|
||||
# La teinte dépend de la colonne et du temps qui passe.
|
||||
pixel(x, y, teinte(x * 8 + temps * 2))
|
||||
montrer()
|
||||
attendre(20)
|
||||
|
||||
|
||||
# --- Animation 2 : la pluie ---
|
||||
def anim_pluie():
|
||||
for image in range(200):
|
||||
assombrir(25) # les gouttes laissent une traînée qui s'estompe
|
||||
|
||||
# Faire descendre toute l'image d'une ligne, en partant du bas
|
||||
# (range(7, 0, -1) compte à rebours : 7, 6, 5… 1).
|
||||
for y in range(HAUTEUR - 1, 0, -1):
|
||||
for x in range(LARGEUR):
|
||||
pixel(x, y, lire_pixel(x, y - 1))
|
||||
|
||||
# La ligne du haut s'éteint, puis 3 nouvelles gouttes au hasard.
|
||||
for x in range(LARGEUR):
|
||||
pixel(x, 0, NOIR)
|
||||
for n in range(3):
|
||||
pixel(random.randrange(LARGEUR), 0, (0, 120, 255))
|
||||
|
||||
montrer()
|
||||
attendre(60)
|
||||
|
||||
|
||||
# --- Animation 3 : un dessin en pixel art qui défile ---
|
||||
# Une lettre par pixel, « . » = éteint. 8 lignes de même longueur.
|
||||
DESSIN = [
|
||||
"..RR.RR..",
|
||||
".RBRRRRR.",
|
||||
".RRRRRRR.",
|
||||
".RRRRRRR.",
|
||||
"..RRRRR..",
|
||||
"...RRR...",
|
||||
"....R....",
|
||||
".........",
|
||||
]
|
||||
|
||||
# Un « dictionnaire » associe chaque lettre à une couleur.
|
||||
COULEUR_DE_LETTRE = {
|
||||
"R": ROUGE, "O": ORANGE, "J": JAUNE, "V": VERT, "C": CYAN,
|
||||
"B": BLANC, "L": BLEU, "M": MAGENTA, "P": ROSE,
|
||||
}
|
||||
|
||||
|
||||
def dessiner_dessin(depart_x):
|
||||
for y, ligne in enumerate(DESSIN): # enumerate donne le numéro ET la ligne
|
||||
for x, lettre in enumerate(ligne):
|
||||
if lettre in COULEUR_DE_LETTRE:
|
||||
pixel(depart_x + x, y, COULEUR_DE_LETTRE[lettre])
|
||||
|
||||
|
||||
def anim_dessin_qui_defile():
|
||||
largeur_dessin = len(DESSIN[0]) # len = longueur de la ligne
|
||||
for x in range(LARGEUR, -largeur_dessin - 1, -1):
|
||||
effacer()
|
||||
dessiner_dessin(x)
|
||||
montrer()
|
||||
attendre(70)
|
||||
|
||||
|
||||
# --- Animation 4 : étincelles ---
|
||||
def anim_etincelles():
|
||||
for image in range(300):
|
||||
assombrir(8)
|
||||
pixel(random.randrange(LARGEUR), random.randrange(HAUTEUR),
|
||||
teinte(random.randrange(256), saturation=200))
|
||||
montrer()
|
||||
attendre(15)
|
||||
|
||||
|
||||
# --- Exemple : votre première animation ---
|
||||
def anim_ma_premiere():
|
||||
for x in range(LARGEUR):
|
||||
effacer()
|
||||
pixel(x, 3, JAUNE)
|
||||
pixel(x, 4, JAUNE)
|
||||
montrer()
|
||||
attendre(50)
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# LA PLAYLIST
|
||||
# =============================================================================
|
||||
# Mettez # devant une ligne pour sauter une animation.
|
||||
|
||||
def playlist():
|
||||
anim_arc_en_ciel()
|
||||
anim_pluie()
|
||||
anim_dessin_qui_defile()
|
||||
anim_etincelles()
|
||||
# anim_ma_premiere()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
demarrer()
|
||||
try:
|
||||
while True: # en boucle, comme loop() sur l'Arduino
|
||||
playlist()
|
||||
if "--enregistrer" in sys.argv:
|
||||
break # en enregistrement, un seul tour suffit
|
||||
except KeyboardInterrupt:
|
||||
print("\nArrêt demandé.")
|
||||
finally:
|
||||
eteindre()
|
||||
@@ -0,0 +1,210 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
=============================================================================
|
||||
GÉNÉRER LES ANIMATIONS D'EXEMPLE (et la palette Krita)
|
||||
Workshop « Vitrine pixels »
|
||||
=============================================================================
|
||||
|
||||
Fabrique, dans le dossier « animations », un exemple de chaque format que
|
||||
le lecteur sait jouer :
|
||||
|
||||
01_coeur_qui_bat.gif un GIF animé (comme un export Krita en GIF)
|
||||
02_texte_defilant.gif un texte qui défile, écrit avec une police 5 × 7
|
||||
03_vague/ une SÉQUENCE de PNG numérotés + images_par_seconde.txt
|
||||
(comme un export Krita en « séquence d'images »)
|
||||
04_balle.png un PNG animé (APNG)
|
||||
|
||||
Et la palette Krita « krita/palette_vitrine_pixels.gpl ».
|
||||
|
||||
Usage :
|
||||
python3 outils/generer_exemples.py
|
||||
|
||||
C'est aussi un exemple : on peut FABRIQUER des images par le code avec
|
||||
Pillow, puis les jouer comme des dessins.
|
||||
"""
|
||||
|
||||
import math
|
||||
import os
|
||||
|
||||
from PIL import Image
|
||||
|
||||
ICI = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
ANIMATIONS = os.path.join(ICI, "animations")
|
||||
L, H = 32, 8
|
||||
|
||||
|
||||
def nouvelle_image():
|
||||
return Image.new("RGB", (L, H))
|
||||
|
||||
|
||||
def dessiner(image, motif, x0, y0, couleurs):
|
||||
"""Dessine un motif texte (une lettre = une couleur, « . » = rien)."""
|
||||
for y, ligne in enumerate(motif):
|
||||
for x, lettre in enumerate(ligne):
|
||||
if lettre in couleurs and 0 <= x0 + x < L and 0 <= y0 + y < H:
|
||||
image.putpixel((x0 + x, y0 + y), couleurs[lettre])
|
||||
|
||||
|
||||
# --- 01 : cœur qui bat (GIF) -------------------------------------------------
|
||||
|
||||
PETIT_COEUR = [
|
||||
".RR.RR.",
|
||||
"RBRRRRR",
|
||||
"RRRRRRR",
|
||||
".RRRRR.",
|
||||
"..RRR..",
|
||||
"...R...",
|
||||
]
|
||||
GRAND_COEUR = [
|
||||
".RRR.RRR.",
|
||||
"RBBRRRRRR",
|
||||
"RBRRRRRRR",
|
||||
"RRRRRRRRR",
|
||||
".RRRRRRR.",
|
||||
"..RRRRR..",
|
||||
"...RRR...",
|
||||
"....R....",
|
||||
]
|
||||
|
||||
|
||||
def coeur_qui_bat():
|
||||
couleurs = {"R": (230, 0, 40), "B": (255, 170, 190)}
|
||||
images, durees = [], []
|
||||
# petit → grand → petit → pause : le rythme d'un battement « boum-boum »
|
||||
for motif, duree in [(PETIT_COEUR, 400), (GRAND_COEUR, 120), (PETIT_COEUR, 120),
|
||||
(GRAND_COEUR, 160)]:
|
||||
im = nouvelle_image()
|
||||
largeur = len(motif[0])
|
||||
dessiner(im, motif, (L - largeur) // 2, (H - len(motif)) // 2 + (len(motif) == 6), couleurs)
|
||||
images.append(im)
|
||||
durees.append(duree)
|
||||
images[0].save(os.path.join(ANIMATIONS, "01_coeur_qui_bat.gif"), save_all=True,
|
||||
append_images=images[1:], duration=durees, loop=0)
|
||||
|
||||
|
||||
# --- 02 : texte défilant (GIF) -----------------------------------------------
|
||||
|
||||
# Police 5 × 7 : chaque lettre est dessinée avec des « # ».
|
||||
POLICE = {
|
||||
"A": [".###.", "#...#", "#...#", "#####", "#...#", "#...#", "#...#"],
|
||||
"D": ["####.", "#...#", "#...#", "#...#", "#...#", "#...#", "####."],
|
||||
"E": ["#####", "#....", "#....", "####.", "#....", "#....", "#####"],
|
||||
"H": ["#...#", "#...#", "#...#", "#####", "#...#", "#...#", "#...#"],
|
||||
"I": ["###", ".#.", ".#.", ".#.", ".#.", ".#.", "###"],
|
||||
"L": ["#....", "#....", "#....", "#....", "#....", "#....", "#####"],
|
||||
"N": ["#...#", "##..#", "#.#.#", "#..##", "#...#", "#...#", "#...#"],
|
||||
"P": ["####.", "#...#", "#...#", "####.", "#....", "#....", "#...."],
|
||||
"R": ["####.", "#...#", "#...#", "####.", "#.#..", "#..#.", "#...#"],
|
||||
"S": [".####", "#....", "#....", ".###.", "....#", "....#", "####."],
|
||||
"T": ["#####", "..#..", "..#..", "..#..", "..#..", "..#..", "..#.."],
|
||||
"V": ["#...#", "#...#", "#...#", "#...#", "#...#", ".#.#.", "..#.."],
|
||||
"X": ["#...#", "#...#", ".#.#.", "..#..", ".#.#.", "#...#", "#...#"],
|
||||
" ": ["...", "...", "...", "...", "...", "...", "..."],
|
||||
}
|
||||
|
||||
|
||||
def texte_defilant(texte="VITRINE PIXELS"):
|
||||
# 1) On écrit tout le texte sur une longue bande.
|
||||
largeur_totale = sum(len(POLICE[c][0]) + 1 for c in texte)
|
||||
bande = Image.new("RGB", (largeur_totale, H))
|
||||
x = 0
|
||||
for i, c in enumerate(texte):
|
||||
# chaque lettre a sa propre teinte : un petit arc-en-ciel
|
||||
r, v, b = [int(255 * (0.5 + 0.5 * math.cos(2 * math.pi * (i / len(texte) + k / 3))))
|
||||
for k in range(3)]
|
||||
for y, ligne in enumerate(POLICE[c]):
|
||||
for dx, p in enumerate(ligne):
|
||||
if p == "#":
|
||||
bande.putpixel((x + dx, y), (r, v, b))
|
||||
x += len(POLICE[c][0]) + 1
|
||||
|
||||
# 2) On fait glisser une « fenêtre » de 32 pixels sur la bande.
|
||||
images = []
|
||||
for decalage in range(-L, largeur_totale + 1):
|
||||
im = nouvelle_image()
|
||||
im.paste(bande, (-decalage, 0))
|
||||
images.append(im)
|
||||
images[0].save(os.path.join(ANIMATIONS, "02_texte_defilant.gif"), save_all=True,
|
||||
append_images=images[1:], duration=60, loop=0)
|
||||
|
||||
|
||||
# --- 03 : vague (séquence de PNG) --------------------------------------------
|
||||
|
||||
def vague(nb_images=40, ips=20):
|
||||
dossier = os.path.join(ANIMATIONS, "03_vague")
|
||||
os.makedirs(dossier, exist_ok=True)
|
||||
for n in range(nb_images):
|
||||
im = nouvelle_image()
|
||||
phase = 2 * math.pi * n / nb_images
|
||||
for x in range(L):
|
||||
hauteur = (H - 1) / 2 * (1 + math.sin(2 * math.pi * x / L + phase))
|
||||
for y in range(H):
|
||||
if H - 1 - y <= hauteur: # sous la crête de la vague
|
||||
# 3 bleus seulement, du plus clair (crête) au plus profond :
|
||||
# un rendu « pixel art » franc, sans dégradé flou.
|
||||
profondeur = min(2, int(hauteur - (H - 1 - y)))
|
||||
im.putpixel((x, y), [(120, 220, 255), (0, 120, 255), (0, 30, 160)][profondeur])
|
||||
# Même nommage que Krita : nom_0000.png, nom_0001.png…
|
||||
im.save(os.path.join(dossier, f"vague_{n:04d}.png"))
|
||||
with open(os.path.join(dossier, "images_par_seconde.txt"), "w") as f:
|
||||
f.write(f"{ips}\n")
|
||||
|
||||
|
||||
# --- 04 : balle qui rebondit (APNG) ------------------------------------------
|
||||
|
||||
def balle(nb_images=48):
|
||||
images = []
|
||||
for n in range(nb_images):
|
||||
im = nouvelle_image()
|
||||
t = n / nb_images
|
||||
x = round((L - 2) * (1 - abs(2 * t - 1))) # aller-retour
|
||||
y = round((H - 2) * (1 - abs(math.sin(4 * math.pi * t)))) # rebonds
|
||||
for dx in (0, 1):
|
||||
for dy in (0, 1):
|
||||
im.putpixel((x + dx, y + dy), (255, 150, 0))
|
||||
images.append(im)
|
||||
images[0].save(os.path.join(ANIMATIONS, "04_balle.png"), save_all=True,
|
||||
append_images=images[1:], duration=40, loop=0)
|
||||
|
||||
|
||||
# --- Palette Krita -----------------------------------------------------------
|
||||
|
||||
def palette_krita():
|
||||
"""Palette au format GIMP (.gpl), que Krita sait importer.
|
||||
Des couleurs SATURÉES, qui rendent bien sur des LED, en 3 intensités,
|
||||
plus les pastels (mélange avec du blanc) et le noir/blanc."""
|
||||
teintes = [("Rouge", (255, 0, 0)), ("Orange", (255, 90, 0)), ("Jaune", (255, 200, 0)),
|
||||
("Vert", (0, 255, 0)), ("Cyan", (0, 255, 200)), ("Bleu", (0, 60, 255)),
|
||||
("Violet", (140, 0, 255)), ("Rose", (255, 0, 110))]
|
||||
lignes = ["GIMP Palette", "Name: Vitrine pixels (LED)", "Columns: 8", "#"]
|
||||
|
||||
def ajouter(c, nom):
|
||||
lignes.append(f"{c[0]:3d} {c[1]:3d} {c[2]:3d}\t{nom}")
|
||||
|
||||
for nom, c in teintes:
|
||||
ajouter(c, nom)
|
||||
for nom, c in teintes: # 60 % : valeur minimum conseillée pour les « moyens »
|
||||
ajouter(tuple(round(k * 0.6) for k in c), nom + " moyen")
|
||||
for nom, c in teintes: # 35 % : la plus sombre qui reste bien visible
|
||||
ajouter(tuple(round(k * 0.35) for k in c), nom + " sombre")
|
||||
for nom, c in teintes:
|
||||
ajouter(tuple(round(k + (255 - k) * 0.55) for k in c), nom + " pastel")
|
||||
for nom, c in [("Noir (éteint)", (0, 0, 0)), ("Gris sombre", (90, 90, 90)),
|
||||
("Gris", (160, 160, 160)), ("Blanc chaud", (255, 200, 140)),
|
||||
("Blanc", (255, 255, 255))]:
|
||||
ajouter(c, nom)
|
||||
|
||||
os.makedirs(os.path.join(ICI, "krita"), exist_ok=True)
|
||||
with open(os.path.join(ICI, "krita", "palette_vitrine_pixels.gpl"), "w") as f:
|
||||
f.write("\n".join(lignes) + "\n")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
os.makedirs(ANIMATIONS, exist_ok=True)
|
||||
coeur_qui_bat()
|
||||
texte_defilant()
|
||||
vague()
|
||||
balle()
|
||||
palette_krita()
|
||||
print("Exemples créés dans", ANIMATIONS)
|
||||
print("Palette créée dans", os.path.join(ICI, "krita"))
|
||||
@@ -0,0 +1,316 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Dessine les schémas du dossier « schemas » :
|
||||
- branchement.svg : Raspberry Pi + 74AHCT125 + matrice + alimentation
|
||||
- brochage_74ahct125.svg : la puce adaptatrice de niveau, patte par patte
|
||||
- chaine_krita_matrice.svg : le trajet d'une animation, de Krita à la matrice
|
||||
Usage : python3 outils/schemas.py"""
|
||||
import os
|
||||
|
||||
ICI = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
SORTIE = os.path.join(ICI, "schemas")
|
||||
|
||||
ROUGE, NOIR, BLANC_FIL, VERT = "#d62828", "#1a1a1a", "#bdbdbd", "#2a9d3a"
|
||||
JAUNE_GPIO = "#e0a800"
|
||||
|
||||
|
||||
class Svg:
|
||||
def __init__(self, l, h, titre):
|
||||
self.l, self.h = l, h
|
||||
self.out = [f'<svg xmlns="http://www.w3.org/2000/svg" width="{l}" height="{h}" '
|
||||
f'viewBox="0 0 {l} {h}" font-family="Helvetica, Arial, sans-serif">',
|
||||
f' <title>{titre}</title>',
|
||||
' <defs><marker id="fleche" viewBox="0 0 10 10" refX="8" refY="5" '
|
||||
'markerWidth="7" markerHeight="7" orient="auto"><path d="M1 1 L9 5 L1 9 Z" '
|
||||
'fill="context-stroke"/></marker>'
|
||||
'<marker id="flecheg" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="6" '
|
||||
'markerHeight="6" orient="auto"><path d="M1 1 L9 5 L1 9 Z" fill="#555"/></marker>'
|
||||
'<marker id="flechec" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="7" '
|
||||
'markerHeight="7" orient="auto"><path d="M1 1 L9 5 L1 9 Z" fill="#cfcfcf"/></marker>'
|
||||
'</defs>',
|
||||
f' <rect width="{l}" height="{h}" fill="#ffffff"/>']
|
||||
|
||||
def add(self, s):
|
||||
self.out.append(" " + s)
|
||||
|
||||
def rect(self, x, y, w, h, fill, rx=0, stroke=None, sw=1):
|
||||
st = f' stroke="{stroke}" stroke-width="{sw}"' if stroke else ""
|
||||
self.add(f'<rect x="{x}" y="{y}" width="{w}" height="{h}" rx="{rx}" fill="{fill}"{st}/>')
|
||||
|
||||
def texte(self, x, y, s, taille=13, c="#333", ancre="start", gras=False, mono=False):
|
||||
g = ' font-weight="bold"' if gras else ""
|
||||
m = ' font-family="monospace"' if mono else ""
|
||||
self.add(f'<text x="{x}" y="{y}" font-size="{taille}" fill="{c}" '
|
||||
f'text-anchor="{ancre}"{g}{m}>{s}</text>')
|
||||
|
||||
def fil(self, d, c, w=5):
|
||||
self.add(f'<path d="{d}" stroke="{c}" stroke-width="{w}" fill="none" '
|
||||
'stroke-linecap="round" stroke-linejoin="round"/>')
|
||||
|
||||
def point(self, x, y, c, r=5):
|
||||
self.add(f'<circle cx="{x}" cy="{y}" r="{r}" fill="{c}"/>')
|
||||
|
||||
def save(self, nom):
|
||||
self.out.append("</svg>")
|
||||
with open(os.path.join(SORTIE, nom), "w") as f:
|
||||
f.write("\n".join(self.out) + "\n")
|
||||
|
||||
|
||||
def hop_h(x_croise, sens=1, r=7):
|
||||
"""Petit pont (demi-cercle) pour un fil horizontal qui en enjambe un autre.
|
||||
sens = 1 : on va vers la droite ; -1 : vers la gauche."""
|
||||
return (f"H{x_croise - sens * r} a{r} {r} 0 0 {1 if sens == 1 else 0} "
|
||||
f"{2 * r * sens} 0")
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# 1. BRANCHEMENT
|
||||
# =============================================================================
|
||||
|
||||
def branchement():
|
||||
s = Svg(1200, 940, "Branchement matrice WS2812B 8×32, Raspberry Pi, 74AHCT125 "
|
||||
"et alimentation d'établi")
|
||||
|
||||
# ---------- matrice (vue de dos) ----------
|
||||
s.texte(60, 38, "Matrice WS2812B 8 × 32 — vue de dos (côté fils)", 20, "#222", gras=True)
|
||||
s.add('<pattern id="pix" x="60" y="55" width="30.625" height="30.625" '
|
||||
'patternUnits="userSpaceOnUse"><rect x="9" y="9" width="12" height="12" rx="1" '
|
||||
'fill="#2b2b2b"/></pattern>')
|
||||
s.rect(60, 55, 980, 245, "#141414", 6)
|
||||
s.rect(60, 55, 980, 245, "url(#pix)", 6)
|
||||
s.add('<line x1="760" y1="150" x2="340" y2="150" stroke="#cfcfcf" stroke-width="3" '
|
||||
'marker-end="url(#flechec)"/>')
|
||||
s.texte(550, 135, "les données entrent par DIN et traversent les 256 LED", 15, "#e6e6e6", "middle")
|
||||
for x, t in [(110, "5V"), (150, "GND"), (194, "DOUT"), (490, "5V"), (550, "GND"),
|
||||
(880, "5V"), (910, "GND"), (940, "DIN")]:
|
||||
s.texte(x, 268, t, 12, "#cfcfcf", "middle")
|
||||
|
||||
# ---------- sortie DOUT ----------
|
||||
for x, c in [(110, ROUGE), (150, BLANC_FIL), (190, VERT)]:
|
||||
s.fil(f"M{x} 282 V340", c)
|
||||
s.rect(96, 340, 108, 24, "#222", 3)
|
||||
s.texte(150, 390, "Sortie DOUT : ne rien brancher", 14, "#555", "middle")
|
||||
s.texte(150, 408, "(sert à chaîner un 2e panneau)", 13, "#888", "middle")
|
||||
|
||||
# ---------- alimentation de la matrice (fils du milieu) ----------
|
||||
s.fil("M490 282 V440 H530 V586", ROUGE, 6)
|
||||
s.fil("M550 282 V420 H590 V586", NOIR, 6)
|
||||
s.add('<line x1="490" y1="352" x2="506" y2="352" stroke="#777" stroke-width="2"/>')
|
||||
s.add('<line x1="534" y1="352" x2="550" y2="352" stroke="#777" stroke-width="2"/>')
|
||||
s.rect(506, 340, 28, 24, "#1f4e9c", 5)
|
||||
s.rect(524, 340, 10, 24, "#c9d6ee")
|
||||
s.texte(529, 356, "−", 13, "#1f4e9c", "middle", True)
|
||||
s.texte(474, 340, "Condensateur 1000 µF", 14, "#1f4e9c", "end", True)
|
||||
s.texte(474, 358, "bande « − » côté GND (fil noir)", 13, "#1f4e9c", "end")
|
||||
s.texte(474, 375, "patte + côté 5V (fil rouge)", 13, "#1f4e9c", "end")
|
||||
|
||||
# ---------- alimentation d'établi ----------
|
||||
s.rect(300, 500, 330, 210, "#e9ecef", 10, "#adb5bd", 2)
|
||||
s.texte(465, 526, "Alimentation d'établi", 16, "#333", "middle", True)
|
||||
s.rect(318, 540, 160, 96, "#1d2b1d", 4)
|
||||
s.texte(398, 578, "5.00 V", 28, "#7dff7d", "middle", True, True)
|
||||
s.texte(398, 618, "3.00 A", 20, "#ffb347", "middle", mono=True)
|
||||
for x, t in [(345, "tension"), (420, "courant")]:
|
||||
s.add(f'<circle cx="{x}" cy="672" r="16" fill="#666"/>')
|
||||
s.texte(x, 702, t, 11, "#555", "middle")
|
||||
s.add(f'<circle cx="530" cy="600" r="13" fill="{ROUGE}" stroke="#7a0f0f" stroke-width="2"/>')
|
||||
s.add(f'<circle cx="590" cy="600" r="13" fill="{NOIR}" stroke="#000" stroke-width="2"/>')
|
||||
s.texte(530, 637, "+", 15, ROUGE, "middle", True)
|
||||
s.texte(590, 637, "−", 15, NOIR, "middle", True)
|
||||
s.texte(560, 660, "bornes de", 12, "#555", "middle")
|
||||
s.texte(560, 675, "sortie", 12, "#555", "middle")
|
||||
|
||||
# ---------- entrée DIN + fiche ----------
|
||||
for x, c in [(880, ROUGE), (910, BLANC_FIL), (940, VERT)]:
|
||||
s.fil(f"M{x} 282 V340", c)
|
||||
s.rect(866, 340, 88, 22, "#222", 3)
|
||||
s.rect(870, 364, 80, 22, "#3a3a3a", 3)
|
||||
s.texte(966, 358, "connecteur DIN de la matrice", 13, "#555")
|
||||
s.texte(966, 381, "+ fiche fournie (rouge / blanc / vert)", 13, "#555")
|
||||
|
||||
# ---------- plaque d'essai + 74AHCT125 ----------
|
||||
s.rect(700, 425, 440, 190, "#f6f1e3", 8, "#d9cfb2", 2)
|
||||
s.texte(1132, 608, "plaque d'essai", 12, "#a8997a", "end")
|
||||
|
||||
def broche_bas(n): # pattes 1 à 7 (rangée du bas), de gauche à droite
|
||||
return 815 + (n - 1) * 28
|
||||
|
||||
def broche_haut(n): # pattes 14 à 8 (rangée du haut), de gauche à droite
|
||||
return 815 + (14 - n) * 28
|
||||
|
||||
# fils de la fiche vers la plaque
|
||||
s.fil(f"M880 386 V444 H{broche_haut(14)} V462", ROUGE) # 5V → VCC (14)
|
||||
s.fil(f"M910 386 V454 {hop_h(broche_haut(14), -1)} H780 V585", BLANC_FIL) # GND → rail
|
||||
s.fil(f"M940 386 V444 H1080 V470", VERT) # DIN → résistance
|
||||
s.fil(f"M1080 510 V552 {hop_h(broche_bas(7), -1)} H{broche_bas(3)} V528", VERT)
|
||||
# rail de masse
|
||||
s.fil(f"M780 585 {hop_h(broche_bas(2), 1)} H{broche_bas(7)}", NOIR, 4)
|
||||
s.fil(f"M{broche_bas(1)} 528 V585", NOIR, 4) # 1OE → GND
|
||||
s.fil(f"M{broche_bas(7)} 528 V585", NOIR, 4) # GND (7)
|
||||
for x in (780, broche_bas(1), broche_bas(7)):
|
||||
s.point(x, 585, NOIR)
|
||||
|
||||
# la puce
|
||||
s.rect(805, 470, 192, 50, "#222", 4)
|
||||
s.add('<path d="M805 485 a10 10 0 0 1 0 20" fill="#555"/>')
|
||||
s.texte(901, 500, "74AHCT125", 15, "#eee", "middle", True)
|
||||
for n in range(1, 8):
|
||||
s.rect(broche_bas(n) - 5, 520, 10, 8, "#b8b8b8")
|
||||
s.texte(broche_bas(n), 515, str(n), 9, "#bbb", "middle")
|
||||
for n in range(8, 15):
|
||||
s.rect(broche_haut(n) - 5, 462, 10, 8, "#b8b8b8")
|
||||
s.texte(broche_haut(n), 482, str(n), 9, "#bbb", "middle")
|
||||
s.texte(broche_bas(1) - 10, 545, "1OE", 11, "#333", "end", True)
|
||||
s.texte(broche_bas(2) + 6, 545, "1A", 11, JAUNE_GPIO, "start", True)
|
||||
s.texte(broche_bas(3) + 6, 545, "1Y", 11, VERT, "start", True)
|
||||
s.texte(broche_bas(7) + 8, 545, "GND", 11, "#333", "start", True)
|
||||
|
||||
# résistance 330 Ω
|
||||
s.rect(1071, 470, 18, 40, "#e8d3a8", 6, "#a88a52")
|
||||
for y, c in [(478, "#e67e22"), (486, "#e67e22"), (494, "#8b4513")]:
|
||||
s.rect(1071, y, 18, 4, c)
|
||||
s.rect(1071, 502, 18, 3, "#c9a227")
|
||||
s.texte(1098, 486, "330 Ω", 13, "#7a5a1e", gras=True)
|
||||
|
||||
s.texte(966, 404, "fil rouge de la fiche : il alimente", 12, ROUGE, gras=True)
|
||||
s.texte(966, 418, "la puce (patte 14 = VCC, 5 V)", 12, ROUGE, gras=True)
|
||||
|
||||
# ---------- Raspberry Pi ----------
|
||||
s.rect(700, 645, 440, 230, "#1f7a3a", 12)
|
||||
s.rect(750, 655, 336, 42, "#143d20", 3)
|
||||
x0 = broche_bas(2) - 80 # x de la patte 2 → la patte 12 tombe sous « 1A »
|
||||
for i in range(20):
|
||||
for rang, y in ((0, 666), (1, 684)): # rangée du bord : pattes paires
|
||||
n = 2 * i + 2 if rang == 0 else 2 * i + 1
|
||||
couleur = "#d4af37"
|
||||
if n in (2, 4):
|
||||
couleur = ROUGE
|
||||
elif n in (6, 14):
|
||||
couleur = NOIR
|
||||
elif n == 12:
|
||||
couleur = JAUNE_GPIO
|
||||
s.rect(x0 + i * 16 - 5, y - 5, 10, 10, couleur, 1)
|
||||
s.texte(x0 - 9, 670, "2", 9, "#cde", "end")
|
||||
s.texte(x0 - 9, 688, "1", 9, "#cde", "end")
|
||||
s.texte(x0 + 19 * 16 + 9, 670, "40", 9, "#cde")
|
||||
s.texte(x0 + 19 * 16 + 9, 688, "39", 9, "#cde")
|
||||
|
||||
# GPIO 18 (patte 12) → 1A (patte 2)
|
||||
s.fil(f"M{broche_bas(2)} 528 V661", JAUNE_GPIO)
|
||||
# GND (patte 6) ← rail
|
||||
s.fil(f"M780 585 V630 H{x0 + 32} V661", NOIR, 4)
|
||||
|
||||
s.texte(920, 770, "Raspberry Pi", 24, "#fff", "middle", True)
|
||||
s.texte(920, 794, "3B+, 4 ou Zero 2 W (pas le Pi 5)", 14, "#d8f0dd", "middle")
|
||||
s.texte(x0 + 80, 712, "patte 12 = GPIO 18", 12, "#ffe08a", "start", True)
|
||||
s.texte(x0 + 30, 712, "patte 6 = GND", 12, "#fff", "end", True)
|
||||
s.add(f'<path d="M{x0 + 8} 833 L{x0 + 24} 849 M{x0 + 24} 833 L{x0 + 8} 849" '
|
||||
f'stroke="#ffb3b3" stroke-width="3"/>')
|
||||
s.texte(x0 + 34, 846, "pattes 2 et 4 (5 V) : NE PAS relier à la matrice", 12, "#ffd6d6")
|
||||
|
||||
# alimentation USB du Pi
|
||||
s.rect(1140, 790, 18, 34, "#b8b8b8", 3)
|
||||
s.fil("M1158 807 H1180 V900", "#555")
|
||||
s.texte(1170, 918, "bloc secteur USB du Pi", 13, "#333", "end")
|
||||
s.texte(1170, 935, "(5 V, 2,5 A ou plus)", 12, "#777", "end")
|
||||
|
||||
# ---------- légende ----------
|
||||
s.rect(40, 470, 236, 300, "#f8f9fa", 8, "#dee2e6")
|
||||
s.texte(56, 496, "Code couleur des fils", 15, "#222", gras=True)
|
||||
for i, (c, t) in enumerate([(ROUGE, "rouge = + 5 V"), (NOIR, "noir = GND (masse)"),
|
||||
(BLANC_FIL, "blanc = GND (masse)"), (VERT, "vert = données 5 V"),
|
||||
(JAUNE_GPIO, "jaune = données 3,3 V")]):
|
||||
y = 520 + i * 28
|
||||
s.add(f'<line x1="56" y1="{y}" x2="90" y2="{y}" stroke="{c}" stroke-width="6" '
|
||||
'stroke-linecap="round"/>')
|
||||
s.texte(100, y + 5, t, 14)
|
||||
s.texte(56, 670, "Tous les GND sont reliés :", 13, "#555")
|
||||
s.texte(56, 688, "Pi ↔ puce ↔ fil blanc ↔ matrice", 13, "#555")
|
||||
s.texte(56, 706, "↔ fil noir ↔ borne − de l'alim.", 13, "#555")
|
||||
s.texte(56, 730, "Alim. réglée sur 5,0 V maxi !", 13, "#b00020", gras=True)
|
||||
s.texte(56, 752, "Un pont = les fils se croisent", 12, "#777")
|
||||
s.texte(56, 766, "sans se toucher.", 12, "#777")
|
||||
|
||||
s.save("branchement.svg")
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# 2. BROCHAGE DU 74AHCT125
|
||||
# =============================================================================
|
||||
|
||||
def brochage():
|
||||
s = Svg(820, 470, "Brochage du 74AHCT125 (vu de dessus)")
|
||||
s.texte(410, 36, "74AHCT125 vu de DESSUS (encoche à gauche)", 20, "#222", "middle", True)
|
||||
s.rect(250, 150, 320, 110, "#222", 6)
|
||||
s.add('<path d="M250 185 a20 20 0 0 1 0 40" fill="#555"/>')
|
||||
s.add('<circle cx="275" cy="240" r="6" fill="#555"/>')
|
||||
s.texte(410, 212, "74AHCT125", 24, "#eee", "middle", True)
|
||||
|
||||
bas = {1: ("1OE", "→ GND", "#333"), 2: ("1A", "← GPIO 18", JAUNE_GPIO),
|
||||
3: ("1Y", "→ 330 Ω → DIN", VERT), 4: ("2OE", "", "#aaa"), 5: ("2A", "", "#aaa"),
|
||||
6: ("2Y", "", "#aaa"), 7: ("GND", "→ GND", "#333")}
|
||||
haut = {14: ("VCC", "← 5 V", ROUGE), 13: ("4OE", "", "#aaa"), 12: ("4A", "", "#aaa"),
|
||||
11: ("4Y", "", "#aaa"), 10: ("3OE", "", "#aaa"), 9: ("3A", "", "#aaa"),
|
||||
8: ("3Y", "", "#aaa")}
|
||||
for n, (nom, role, c) in bas.items():
|
||||
x = 280 + (n - 1) * 43
|
||||
s.rect(x - 8, 260, 16, 26, "#b8b8b8")
|
||||
s.texte(x, 252, str(n), 12, "#bbb", "middle")
|
||||
s.texte(x, 306, nom, 14, c, "middle", True)
|
||||
if role:
|
||||
y = 338 + {1: 0, 2: 22, 3: 44, 7: 0}[n] * 1
|
||||
s.add(f'<line x1="{x}" y1="312" x2="{x}" y2="{y - 13}" stroke="{c}" stroke-width="1.5"/>')
|
||||
s.texte(x, y, role, 13, c, "middle", True)
|
||||
for n, (nom, role, c) in haut.items():
|
||||
x = 280 + (14 - n) * 43
|
||||
s.rect(x - 8, 124, 16, 26, "#b8b8b8")
|
||||
s.texte(x, 168, str(n), 12, "#bbb", "middle")
|
||||
s.texte(x, 114, nom, 14, c, "middle", True)
|
||||
if role:
|
||||
s.texte(x, 92, role, 13, c, "middle", True)
|
||||
|
||||
s.texte(40, 445, "On n'utilise qu'une des 4 « portes » (la n° 1). Pattes en gris : non branchées "
|
||||
"(on peut relier 4, 10 et 13 au 5 V pour les désactiver).", 13, "#555")
|
||||
s.save("brochage_74ahct125.svg")
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# 3. DE KRITA À LA MATRICE
|
||||
# =============================================================================
|
||||
|
||||
def chaine():
|
||||
s = Svg(1200, 330, "De Krita à la matrice : le trajet d'une animation")
|
||||
etapes = [
|
||||
("#7b3fa0", "1. Dessiner", ["Krita", "document 32 × 8 px", "pixel art, sans flou"]),
|
||||
("#b5418f", "2. Exporter", ["GIF animé", "ou séquence de PNG", "(Rendu de l'animation)"]),
|
||||
("#c9682a", "3. Vérifier", ["verifier_animation.py", "+ simulateur", "sur l'ordinateur"]),
|
||||
("#b08900", "4. Déposer", ["dossier « animations »", "carte SD ou réseau", "(01_, 02_… = ordre)"]),
|
||||
("#1f7a3a", "5. Lire", ["Raspberry Pi", "lecteur.py : gamma,", "luminosité, serpentin"]),
|
||||
("#2f6fc4", "6. Afficher", ["74AHCT125 (3,3 → 5 V)", "puis 256 LED", "WS2812B"]),
|
||||
]
|
||||
larg, ecart = 170, 28
|
||||
for i, (c, titre, lignes) in enumerate(etapes):
|
||||
x = 30 + i * (larg + ecart)
|
||||
s.rect(x, 60, larg, 190, "#ffffff", 12, c, 3)
|
||||
s.rect(x, 60, larg, 46, c, 12)
|
||||
s.rect(x, 90, larg, 16, c)
|
||||
s.texte(x + larg / 2, 90, titre, 18, "#fff", "middle", True)
|
||||
for j, t in enumerate(lignes):
|
||||
s.texte(x + larg / 2, 140 + j * 26, t, 14 if j == 0 else 13,
|
||||
"#222" if j == 0 else "#555", "middle", j == 0)
|
||||
if i < len(etapes) - 1:
|
||||
s.add(f'<line x1="{x + larg + 4}" y1="155" x2="{x + larg + ecart - 4}" y2="155" '
|
||||
'stroke="#555" stroke-width="3" marker-end="url(#flecheg)"/>')
|
||||
s.texte(30, 36, "De Krita à la matrice", 22, "#222", gras=True)
|
||||
s.add('<path d="M115 270 V292 H1003 V270" stroke="#aaa" stroke-width="2" fill="none" '
|
||||
'stroke-dasharray="6 5"/>')
|
||||
s.texte(559, 316, "Étapes 1 à 4 : l'équipe design, sur n'importe quel ordinateur. "
|
||||
"Étapes 5 et 6 : automatiques, dans la vitrine.", 14, "#555", "middle")
|
||||
s.save("chaine_krita_matrice.svg")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
os.makedirs(SORTIE, exist_ok=True)
|
||||
branchement()
|
||||
brochage()
|
||||
chaine()
|
||||
print("Schémas écrits dans", SORTIE)
|
||||
@@ -0,0 +1,187 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
=============================================================================
|
||||
VÉRIFIER UNE ANIMATION avant de l'envoyer sur la matrice
|
||||
Workshop « Vitrine pixels »
|
||||
=============================================================================
|
||||
|
||||
Pour l'équipe design : ce programme examine un export de Krita et dit
|
||||
s'il est prêt pour la matrice. Il fonctionne sur n'importe quel ordinateur
|
||||
(il suffit de Python et de Pillow), sans la matrice.
|
||||
|
||||
Usage :
|
||||
python3 outils/verifier_animation.py mon_animation.gif
|
||||
python3 outils/verifier_animation.py dossier_de_png/
|
||||
python3 outils/verifier_animation.py animations/ (tout le dossier)
|
||||
|
||||
Il affiche un petit rapport :
|
||||
- taille de l'image, nombre d'images, durée, vitesse ;
|
||||
- nombre de couleurs (beaucoup de couleurs = souvent de l'anticrénelage,
|
||||
ces pixels « flous » qui adoucissent les bords dans Krita) ;
|
||||
- pixels trop sombres, qui seront ÉTEINTS sur la matrice ;
|
||||
- courant consommé, pour vérifier que l'alimentation suit.
|
||||
|
||||
Et il fabrique, dans le dossier « apercus », deux APERÇUS « façon LED » :
|
||||
- mon_animation_apercu.png : planche avec toutes les images ;
|
||||
- mon_animation_apercu.gif : l'animation agrandie, à partager.
|
||||
(Pas dans le dossier des animations : le lecteur les jouerait aussi !)
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
from PIL import Image, ImageDraw
|
||||
|
||||
# On réutilise le code du lecteur et du moteur (dossier parent).
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
import lecteur # noqa: E402
|
||||
from matrice import LARGEUR, HAUTEUR, TABLE_GAMMA, LUMINOSITE, courant_estime_ma, COURANT_MAX_MA, pixels_de # noqa: E402
|
||||
|
||||
DOSSIER_APERCUS = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "apercus")
|
||||
TAILLE_LED = 14 # taille d'un pixel dans l'aperçu, en points
|
||||
MAX_PLANCHE = 48 # nombre maximum d'images sur la planche
|
||||
|
||||
|
||||
def pixel_eteint_sur_matrice(r, v, b):
|
||||
"""Vrai si ce pixel, allumé dans l'image, sera éteint sur la matrice
|
||||
(une fois la correction gamma et la luminosité appliquées)."""
|
||||
if (r, v, b) == (0, 0, 0):
|
||||
return False
|
||||
return all(TABLE_GAMMA[c] * LUMINOSITE // 255 == 0 for c in (r, v, b))
|
||||
|
||||
|
||||
def dessiner_leds(image, taille=TAILLE_LED):
|
||||
"""Agrandit une image 32 × 8 en dessinant un rond par pixel."""
|
||||
sortie = Image.new("RGB", (LARGEUR * taille, HAUTEUR * taille), (12, 12, 12))
|
||||
crayon = ImageDraw.Draw(sortie)
|
||||
for y in range(HAUTEUR):
|
||||
for x in range(LARGEUR):
|
||||
couleur = image.getpixel((x, y))
|
||||
if couleur == (0, 0, 0):
|
||||
couleur = (30, 30, 30) # LED éteinte : gris très sombre
|
||||
marge = max(1, taille // 7)
|
||||
crayon.ellipse((x * taille + marge, y * taille + marge,
|
||||
(x + 1) * taille - marge, (y + 1) * taille - marge), fill=couleur)
|
||||
return sortie
|
||||
|
||||
|
||||
def verifier(chemin):
|
||||
nom = os.path.basename(os.path.normpath(chemin))
|
||||
print("=" * 70)
|
||||
print(nom)
|
||||
print("=" * 70)
|
||||
|
||||
# Taille d'origine (avant adaptation par le lecteur).
|
||||
if os.path.isdir(chemin):
|
||||
fichiers = lecteur.images_du_dossier(chemin)
|
||||
if not fichiers:
|
||||
print(" ✗ aucun fichier image dans ce dossier")
|
||||
return
|
||||
with Image.open(fichiers[0]) as premiere:
|
||||
taille = premiere.size
|
||||
print(f" Type : séquence de {len(fichiers)} fichiers "
|
||||
f"({lecteur.lire_vitesse(chemin):g} images/s)")
|
||||
else:
|
||||
with Image.open(chemin) as fichier:
|
||||
taille = fichier.size
|
||||
print(f" Type : {fichier.format}, "
|
||||
f"{'animé' if getattr(fichier, 'is_animated', False) else 'image fixe'}")
|
||||
|
||||
etapes = lecteur.charger(chemin)
|
||||
duree = sum(d for _, d in etapes)
|
||||
|
||||
# --- Taille ---
|
||||
if taille == (LARGEUR, HAUTEUR):
|
||||
print(f" Taille : {taille[0]} × {taille[1]} ✓")
|
||||
elif taille[0] % LARGEUR == 0 and taille[0] // LARGEUR == taille[1] // HAUTEUR \
|
||||
and taille[1] % HAUTEUR == 0:
|
||||
print(f" Taille : {taille[0]} × {taille[1]} ✓ (agrandissement × "
|
||||
f"{taille[0] // LARGEUR}, sera réduit)")
|
||||
else:
|
||||
print(f" Taille : {taille[0]} × {taille[1]} ⚠ attendu {LARGEUR} × {HAUTEUR} "
|
||||
"(l'image sera centrée et coupée)")
|
||||
|
||||
# --- Durée ---
|
||||
if len(etapes) > 1:
|
||||
print(f" Images : {len(etapes)}, durée {duree:.2f} s "
|
||||
f"(≈ {len(etapes) / duree:.1f} images/s)")
|
||||
else:
|
||||
print(" Images : 1 (image fixe)")
|
||||
|
||||
# --- Couleurs ---
|
||||
couleurs = set()
|
||||
trop_sombres = 0
|
||||
for image, _ in etapes:
|
||||
for c in pixels_de(image):
|
||||
couleurs.add(c)
|
||||
for c in couleurs:
|
||||
if pixel_eteint_sur_matrice(*c):
|
||||
trop_sombres += 1
|
||||
alerte = " ⚠ beaucoup ! anticrénelage ou dégradé flou ?" if len(couleurs) > 64 else " ✓"
|
||||
print(f" Couleurs : {len(couleurs)} différentes{alerte}")
|
||||
if trop_sombres:
|
||||
print(f" Trop sombre : {trop_sombres} couleur(s) seront ÉTEINTES sur la matrice "
|
||||
f"(à luminosité {LUMINOSITE}) ⚠")
|
||||
else:
|
||||
print(" Trop sombre : aucune ✓")
|
||||
|
||||
# --- Courant ---
|
||||
courants = [courant_estime_ma(image) for image, _ in etapes]
|
||||
maxi = max(courants)
|
||||
moyen = sum(courants) / len(courants)
|
||||
limite = " ⚠ limité par le logiciel (image assombrie)" if maxi >= COURANT_MAX_MA - 1 else " ✓"
|
||||
print(f" Courant : moyen {moyen / 1000:.2f} A, maximum {maxi / 1000:.2f} A{limite}")
|
||||
|
||||
# --- Aperçus ---
|
||||
os.makedirs(DOSSIER_APERCUS, exist_ok=True)
|
||||
base = os.path.join(DOSSIER_APERCUS, os.path.splitext(nom)[0] + "_apercu")
|
||||
images = [dessiner_leds(image) for image, _ in etapes]
|
||||
|
||||
# Planche : les images les unes sous les autres, numérotées.
|
||||
montrees = images[:MAX_PLANCHE]
|
||||
l, h = images[0].size
|
||||
planche = Image.new("RGB", (l + 50, len(montrees) * (h + 6)), (255, 255, 255))
|
||||
crayon = ImageDraw.Draw(planche)
|
||||
for i, im in enumerate(montrees):
|
||||
planche.paste(im, (50, i * (h + 6)))
|
||||
crayon.text((6, i * (h + 6) + h // 2 - 6), str(i + 1), fill=(0, 0, 0))
|
||||
planche.save(base + ".png")
|
||||
print(f" Aperçu : {base}.png", end="")
|
||||
|
||||
if len(images) > 1:
|
||||
durees = [max(20, round(d * 1000)) for _, d in etapes]
|
||||
images[0].save(base + ".gif", save_all=True, append_images=images[1:],
|
||||
duration=durees, loop=0)
|
||||
print(f" et {base}.gif")
|
||||
else:
|
||||
print()
|
||||
|
||||
|
||||
def est_sequence(dossier):
|
||||
"""Vrai si le dossier ne contient que des PNG (et aucun sous-dossier)."""
|
||||
noms = [n for n in os.listdir(dossier) if not lecteur.est_cache(n)
|
||||
and n != "images_par_seconde.txt"]
|
||||
return bool(noms) and all(n.lower().endswith(".png") and
|
||||
os.path.isfile(os.path.join(dossier, n)) for n in noms)
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 2:
|
||||
print(__doc__)
|
||||
sys.exit(1)
|
||||
for chemin in sys.argv[1:]:
|
||||
# Un dossier peut être une SÉQUENCE (que des PNG, pas de sous-dossier)
|
||||
# ou un dossier d'animations ENTIER : on vérifie alors chaque élément.
|
||||
if os.path.isdir(chemin) and not est_sequence(chemin):
|
||||
cibles = lecteur.lister_animations(chemin)
|
||||
else:
|
||||
cibles = [chemin]
|
||||
for cible in cibles:
|
||||
try:
|
||||
verifier(cible)
|
||||
except Exception as erreur:
|
||||
print(f" ✗ impossible de lire {cible} : {erreur}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,188 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="940" viewBox="0 0 1200 940" font-family="Helvetica, Arial, sans-serif">
|
||||
<title>Branchement matrice WS2812B 8×32, Raspberry Pi, 74AHCT125 et alimentation d'établi</title>
|
||||
<defs><marker id="fleche" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="7" markerHeight="7" orient="auto"><path d="M1 1 L9 5 L1 9 Z" fill="context-stroke"/></marker><marker id="flecheg" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="6" markerHeight="6" orient="auto"><path d="M1 1 L9 5 L1 9 Z" fill="#555"/></marker><marker id="flechec" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="7" markerHeight="7" orient="auto"><path d="M1 1 L9 5 L1 9 Z" fill="#cfcfcf"/></marker></defs>
|
||||
<rect width="1200" height="940" fill="#ffffff"/>
|
||||
<text x="60" y="38" font-size="20" fill="#222" text-anchor="start" font-weight="bold">Matrice WS2812B 8 × 32 — vue de dos (côté fils)</text>
|
||||
<pattern id="pix" x="60" y="55" width="30.625" height="30.625" patternUnits="userSpaceOnUse"><rect x="9" y="9" width="12" height="12" rx="1" fill="#2b2b2b"/></pattern>
|
||||
<rect x="60" y="55" width="980" height="245" rx="6" fill="#141414"/>
|
||||
<rect x="60" y="55" width="980" height="245" rx="6" fill="url(#pix)"/>
|
||||
<line x1="760" y1="150" x2="340" y2="150" stroke="#cfcfcf" stroke-width="3" marker-end="url(#flechec)"/>
|
||||
<text x="550" y="135" font-size="15" fill="#e6e6e6" text-anchor="middle">les données entrent par DIN et traversent les 256 LED</text>
|
||||
<text x="110" y="268" font-size="12" fill="#cfcfcf" text-anchor="middle">5V</text>
|
||||
<text x="150" y="268" font-size="12" fill="#cfcfcf" text-anchor="middle">GND</text>
|
||||
<text x="194" y="268" font-size="12" fill="#cfcfcf" text-anchor="middle">DOUT</text>
|
||||
<text x="490" y="268" font-size="12" fill="#cfcfcf" text-anchor="middle">5V</text>
|
||||
<text x="550" y="268" font-size="12" fill="#cfcfcf" text-anchor="middle">GND</text>
|
||||
<text x="880" y="268" font-size="12" fill="#cfcfcf" text-anchor="middle">5V</text>
|
||||
<text x="910" y="268" font-size="12" fill="#cfcfcf" text-anchor="middle">GND</text>
|
||||
<text x="940" y="268" font-size="12" fill="#cfcfcf" text-anchor="middle">DIN</text>
|
||||
<path d="M110 282 V340" stroke="#d62828" stroke-width="5" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M150 282 V340" stroke="#bdbdbd" stroke-width="5" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M190 282 V340" stroke="#2a9d3a" stroke-width="5" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<rect x="96" y="340" width="108" height="24" rx="3" fill="#222"/>
|
||||
<text x="150" y="390" font-size="14" fill="#555" text-anchor="middle">Sortie DOUT : ne rien brancher</text>
|
||||
<text x="150" y="408" font-size="13" fill="#888" text-anchor="middle">(sert à chaîner un 2e panneau)</text>
|
||||
<path d="M490 282 V440 H530 V586" stroke="#d62828" stroke-width="6" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M550 282 V420 H590 V586" stroke="#1a1a1a" stroke-width="6" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<line x1="490" y1="352" x2="506" y2="352" stroke="#777" stroke-width="2"/>
|
||||
<line x1="534" y1="352" x2="550" y2="352" stroke="#777" stroke-width="2"/>
|
||||
<rect x="506" y="340" width="28" height="24" rx="5" fill="#1f4e9c"/>
|
||||
<rect x="524" y="340" width="10" height="24" rx="0" fill="#c9d6ee"/>
|
||||
<text x="529" y="356" font-size="13" fill="#1f4e9c" text-anchor="middle" font-weight="bold">−</text>
|
||||
<text x="474" y="340" font-size="14" fill="#1f4e9c" text-anchor="end" font-weight="bold">Condensateur 1000 µF</text>
|
||||
<text x="474" y="358" font-size="13" fill="#1f4e9c" text-anchor="end">bande « − » côté GND (fil noir)</text>
|
||||
<text x="474" y="375" font-size="13" fill="#1f4e9c" text-anchor="end">patte + côté 5V (fil rouge)</text>
|
||||
<rect x="300" y="500" width="330" height="210" rx="10" fill="#e9ecef" stroke="#adb5bd" stroke-width="2"/>
|
||||
<text x="465" y="526" font-size="16" fill="#333" text-anchor="middle" font-weight="bold">Alimentation d'établi</text>
|
||||
<rect x="318" y="540" width="160" height="96" rx="4" fill="#1d2b1d"/>
|
||||
<text x="398" y="578" font-size="28" fill="#7dff7d" text-anchor="middle" font-weight="bold" font-family="monospace">5.00 V</text>
|
||||
<text x="398" y="618" font-size="20" fill="#ffb347" text-anchor="middle" font-family="monospace">3.00 A</text>
|
||||
<circle cx="345" cy="672" r="16" fill="#666"/>
|
||||
<text x="345" y="702" font-size="11" fill="#555" text-anchor="middle">tension</text>
|
||||
<circle cx="420" cy="672" r="16" fill="#666"/>
|
||||
<text x="420" y="702" font-size="11" fill="#555" text-anchor="middle">courant</text>
|
||||
<circle cx="530" cy="600" r="13" fill="#d62828" stroke="#7a0f0f" stroke-width="2"/>
|
||||
<circle cx="590" cy="600" r="13" fill="#1a1a1a" stroke="#000" stroke-width="2"/>
|
||||
<text x="530" y="637" font-size="15" fill="#d62828" text-anchor="middle" font-weight="bold">+</text>
|
||||
<text x="590" y="637" font-size="15" fill="#1a1a1a" text-anchor="middle" font-weight="bold">−</text>
|
||||
<text x="560" y="660" font-size="12" fill="#555" text-anchor="middle">bornes de</text>
|
||||
<text x="560" y="675" font-size="12" fill="#555" text-anchor="middle">sortie</text>
|
||||
<path d="M880 282 V340" stroke="#d62828" stroke-width="5" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M910 282 V340" stroke="#bdbdbd" stroke-width="5" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M940 282 V340" stroke="#2a9d3a" stroke-width="5" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<rect x="866" y="340" width="88" height="22" rx="3" fill="#222"/>
|
||||
<rect x="870" y="364" width="80" height="22" rx="3" fill="#3a3a3a"/>
|
||||
<text x="966" y="358" font-size="13" fill="#555" text-anchor="start">connecteur DIN de la matrice</text>
|
||||
<text x="966" y="381" font-size="13" fill="#555" text-anchor="start">+ fiche fournie (rouge / blanc / vert)</text>
|
||||
<rect x="700" y="425" width="440" height="190" rx="8" fill="#f6f1e3" stroke="#d9cfb2" stroke-width="2"/>
|
||||
<text x="1132" y="608" font-size="12" fill="#a8997a" text-anchor="end">plaque d'essai</text>
|
||||
<path d="M880 386 V444 H815 V462" stroke="#d62828" stroke-width="5" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M910 386 V454 H822 a7 7 0 0 0 -14 0 H780 V585" stroke="#bdbdbd" stroke-width="5" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M940 386 V444 H1080 V470" stroke="#2a9d3a" stroke-width="5" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M1080 510 V552 H990 a7 7 0 0 0 -14 0 H871 V528" stroke="#2a9d3a" stroke-width="5" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M780 585 H836 a7 7 0 0 1 14 0 H983" stroke="#1a1a1a" stroke-width="4" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M815 528 V585" stroke="#1a1a1a" stroke-width="4" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M983 528 V585" stroke="#1a1a1a" stroke-width="4" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<circle cx="780" cy="585" r="5" fill="#1a1a1a"/>
|
||||
<circle cx="815" cy="585" r="5" fill="#1a1a1a"/>
|
||||
<circle cx="983" cy="585" r="5" fill="#1a1a1a"/>
|
||||
<rect x="805" y="470" width="192" height="50" rx="4" fill="#222"/>
|
||||
<path d="M805 485 a10 10 0 0 1 0 20" fill="#555"/>
|
||||
<text x="901" y="500" font-size="15" fill="#eee" text-anchor="middle" font-weight="bold">74AHCT125</text>
|
||||
<rect x="810" y="520" width="10" height="8" rx="0" fill="#b8b8b8"/>
|
||||
<text x="815" y="515" font-size="9" fill="#bbb" text-anchor="middle">1</text>
|
||||
<rect x="838" y="520" width="10" height="8" rx="0" fill="#b8b8b8"/>
|
||||
<text x="843" y="515" font-size="9" fill="#bbb" text-anchor="middle">2</text>
|
||||
<rect x="866" y="520" width="10" height="8" rx="0" fill="#b8b8b8"/>
|
||||
<text x="871" y="515" font-size="9" fill="#bbb" text-anchor="middle">3</text>
|
||||
<rect x="894" y="520" width="10" height="8" rx="0" fill="#b8b8b8"/>
|
||||
<text x="899" y="515" font-size="9" fill="#bbb" text-anchor="middle">4</text>
|
||||
<rect x="922" y="520" width="10" height="8" rx="0" fill="#b8b8b8"/>
|
||||
<text x="927" y="515" font-size="9" fill="#bbb" text-anchor="middle">5</text>
|
||||
<rect x="950" y="520" width="10" height="8" rx="0" fill="#b8b8b8"/>
|
||||
<text x="955" y="515" font-size="9" fill="#bbb" text-anchor="middle">6</text>
|
||||
<rect x="978" y="520" width="10" height="8" rx="0" fill="#b8b8b8"/>
|
||||
<text x="983" y="515" font-size="9" fill="#bbb" text-anchor="middle">7</text>
|
||||
<rect x="978" y="462" width="10" height="8" rx="0" fill="#b8b8b8"/>
|
||||
<text x="983" y="482" font-size="9" fill="#bbb" text-anchor="middle">8</text>
|
||||
<rect x="950" y="462" width="10" height="8" rx="0" fill="#b8b8b8"/>
|
||||
<text x="955" y="482" font-size="9" fill="#bbb" text-anchor="middle">9</text>
|
||||
<rect x="922" y="462" width="10" height="8" rx="0" fill="#b8b8b8"/>
|
||||
<text x="927" y="482" font-size="9" fill="#bbb" text-anchor="middle">10</text>
|
||||
<rect x="894" y="462" width="10" height="8" rx="0" fill="#b8b8b8"/>
|
||||
<text x="899" y="482" font-size="9" fill="#bbb" text-anchor="middle">11</text>
|
||||
<rect x="866" y="462" width="10" height="8" rx="0" fill="#b8b8b8"/>
|
||||
<text x="871" y="482" font-size="9" fill="#bbb" text-anchor="middle">12</text>
|
||||
<rect x="838" y="462" width="10" height="8" rx="0" fill="#b8b8b8"/>
|
||||
<text x="843" y="482" font-size="9" fill="#bbb" text-anchor="middle">13</text>
|
||||
<rect x="810" y="462" width="10" height="8" rx="0" fill="#b8b8b8"/>
|
||||
<text x="815" y="482" font-size="9" fill="#bbb" text-anchor="middle">14</text>
|
||||
<text x="805" y="545" font-size="11" fill="#333" text-anchor="end" font-weight="bold">1OE</text>
|
||||
<text x="849" y="545" font-size="11" fill="#e0a800" text-anchor="start" font-weight="bold">1A</text>
|
||||
<text x="877" y="545" font-size="11" fill="#2a9d3a" text-anchor="start" font-weight="bold">1Y</text>
|
||||
<text x="991" y="545" font-size="11" fill="#333" text-anchor="start" font-weight="bold">GND</text>
|
||||
<rect x="1071" y="470" width="18" height="40" rx="6" fill="#e8d3a8" stroke="#a88a52" stroke-width="1"/>
|
||||
<rect x="1071" y="478" width="18" height="4" rx="0" fill="#e67e22"/>
|
||||
<rect x="1071" y="486" width="18" height="4" rx="0" fill="#e67e22"/>
|
||||
<rect x="1071" y="494" width="18" height="4" rx="0" fill="#8b4513"/>
|
||||
<rect x="1071" y="502" width="18" height="3" rx="0" fill="#c9a227"/>
|
||||
<text x="1098" y="486" font-size="13" fill="#7a5a1e" text-anchor="start" font-weight="bold">330 Ω</text>
|
||||
<text x="966" y="404" font-size="12" fill="#d62828" text-anchor="start" font-weight="bold">fil rouge de la fiche : il alimente</text>
|
||||
<text x="966" y="418" font-size="12" fill="#d62828" text-anchor="start" font-weight="bold">la puce (patte 14 = VCC, 5 V)</text>
|
||||
<rect x="700" y="645" width="440" height="230" rx="12" fill="#1f7a3a"/>
|
||||
<rect x="750" y="655" width="336" height="42" rx="3" fill="#143d20"/>
|
||||
<rect x="758" y="661" width="10" height="10" rx="1" fill="#d62828"/>
|
||||
<rect x="758" y="679" width="10" height="10" rx="1" fill="#d4af37"/>
|
||||
<rect x="774" y="661" width="10" height="10" rx="1" fill="#d62828"/>
|
||||
<rect x="774" y="679" width="10" height="10" rx="1" fill="#d4af37"/>
|
||||
<rect x="790" y="661" width="10" height="10" rx="1" fill="#1a1a1a"/>
|
||||
<rect x="790" y="679" width="10" height="10" rx="1" fill="#d4af37"/>
|
||||
<rect x="806" y="661" width="10" height="10" rx="1" fill="#d4af37"/>
|
||||
<rect x="806" y="679" width="10" height="10" rx="1" fill="#d4af37"/>
|
||||
<rect x="822" y="661" width="10" height="10" rx="1" fill="#d4af37"/>
|
||||
<rect x="822" y="679" width="10" height="10" rx="1" fill="#d4af37"/>
|
||||
<rect x="838" y="661" width="10" height="10" rx="1" fill="#e0a800"/>
|
||||
<rect x="838" y="679" width="10" height="10" rx="1" fill="#d4af37"/>
|
||||
<rect x="854" y="661" width="10" height="10" rx="1" fill="#1a1a1a"/>
|
||||
<rect x="854" y="679" width="10" height="10" rx="1" fill="#d4af37"/>
|
||||
<rect x="870" y="661" width="10" height="10" rx="1" fill="#d4af37"/>
|
||||
<rect x="870" y="679" width="10" height="10" rx="1" fill="#d4af37"/>
|
||||
<rect x="886" y="661" width="10" height="10" rx="1" fill="#d4af37"/>
|
||||
<rect x="886" y="679" width="10" height="10" rx="1" fill="#d4af37"/>
|
||||
<rect x="902" y="661" width="10" height="10" rx="1" fill="#d4af37"/>
|
||||
<rect x="902" y="679" width="10" height="10" rx="1" fill="#d4af37"/>
|
||||
<rect x="918" y="661" width="10" height="10" rx="1" fill="#d4af37"/>
|
||||
<rect x="918" y="679" width="10" height="10" rx="1" fill="#d4af37"/>
|
||||
<rect x="934" y="661" width="10" height="10" rx="1" fill="#d4af37"/>
|
||||
<rect x="934" y="679" width="10" height="10" rx="1" fill="#d4af37"/>
|
||||
<rect x="950" y="661" width="10" height="10" rx="1" fill="#d4af37"/>
|
||||
<rect x="950" y="679" width="10" height="10" rx="1" fill="#d4af37"/>
|
||||
<rect x="966" y="661" width="10" height="10" rx="1" fill="#d4af37"/>
|
||||
<rect x="966" y="679" width="10" height="10" rx="1" fill="#d4af37"/>
|
||||
<rect x="982" y="661" width="10" height="10" rx="1" fill="#d4af37"/>
|
||||
<rect x="982" y="679" width="10" height="10" rx="1" fill="#d4af37"/>
|
||||
<rect x="998" y="661" width="10" height="10" rx="1" fill="#d4af37"/>
|
||||
<rect x="998" y="679" width="10" height="10" rx="1" fill="#d4af37"/>
|
||||
<rect x="1014" y="661" width="10" height="10" rx="1" fill="#d4af37"/>
|
||||
<rect x="1014" y="679" width="10" height="10" rx="1" fill="#d4af37"/>
|
||||
<rect x="1030" y="661" width="10" height="10" rx="1" fill="#d4af37"/>
|
||||
<rect x="1030" y="679" width="10" height="10" rx="1" fill="#d4af37"/>
|
||||
<rect x="1046" y="661" width="10" height="10" rx="1" fill="#d4af37"/>
|
||||
<rect x="1046" y="679" width="10" height="10" rx="1" fill="#d4af37"/>
|
||||
<rect x="1062" y="661" width="10" height="10" rx="1" fill="#d4af37"/>
|
||||
<rect x="1062" y="679" width="10" height="10" rx="1" fill="#d4af37"/>
|
||||
<text x="754" y="670" font-size="9" fill="#cde" text-anchor="end">2</text>
|
||||
<text x="754" y="688" font-size="9" fill="#cde" text-anchor="end">1</text>
|
||||
<text x="1076" y="670" font-size="9" fill="#cde" text-anchor="start">40</text>
|
||||
<text x="1076" y="688" font-size="9" fill="#cde" text-anchor="start">39</text>
|
||||
<path d="M843 528 V661" stroke="#e0a800" stroke-width="5" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M780 585 V630 H795 V661" stroke="#1a1a1a" stroke-width="4" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<text x="920" y="770" font-size="24" fill="#fff" text-anchor="middle" font-weight="bold">Raspberry Pi</text>
|
||||
<text x="920" y="794" font-size="14" fill="#d8f0dd" text-anchor="middle">3B+, 4 ou Zero 2 W (pas le Pi 5)</text>
|
||||
<text x="843" y="712" font-size="12" fill="#ffe08a" text-anchor="start" font-weight="bold">patte 12 = GPIO 18</text>
|
||||
<text x="793" y="712" font-size="12" fill="#fff" text-anchor="end" font-weight="bold">patte 6 = GND</text>
|
||||
<path d="M771 833 L787 849 M787 833 L771 849" stroke="#ffb3b3" stroke-width="3"/>
|
||||
<text x="797" y="846" font-size="12" fill="#ffd6d6" text-anchor="start">pattes 2 et 4 (5 V) : NE PAS relier à la matrice</text>
|
||||
<rect x="1140" y="790" width="18" height="34" rx="3" fill="#b8b8b8"/>
|
||||
<path d="M1158 807 H1180 V900" stroke="#555" stroke-width="5" fill="none" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<text x="1170" y="918" font-size="13" fill="#333" text-anchor="end">bloc secteur USB du Pi</text>
|
||||
<text x="1170" y="935" font-size="12" fill="#777" text-anchor="end">(5 V, 2,5 A ou plus)</text>
|
||||
<rect x="40" y="470" width="236" height="300" rx="8" fill="#f8f9fa" stroke="#dee2e6" stroke-width="1"/>
|
||||
<text x="56" y="496" font-size="15" fill="#222" text-anchor="start" font-weight="bold">Code couleur des fils</text>
|
||||
<line x1="56" y1="520" x2="90" y2="520" stroke="#d62828" stroke-width="6" stroke-linecap="round"/>
|
||||
<text x="100" y="525" font-size="14" fill="#333" text-anchor="start">rouge = + 5 V</text>
|
||||
<line x1="56" y1="548" x2="90" y2="548" stroke="#1a1a1a" stroke-width="6" stroke-linecap="round"/>
|
||||
<text x="100" y="553" font-size="14" fill="#333" text-anchor="start">noir = GND (masse)</text>
|
||||
<line x1="56" y1="576" x2="90" y2="576" stroke="#bdbdbd" stroke-width="6" stroke-linecap="round"/>
|
||||
<text x="100" y="581" font-size="14" fill="#333" text-anchor="start">blanc = GND (masse)</text>
|
||||
<line x1="56" y1="604" x2="90" y2="604" stroke="#2a9d3a" stroke-width="6" stroke-linecap="round"/>
|
||||
<text x="100" y="609" font-size="14" fill="#333" text-anchor="start">vert = données 5 V</text>
|
||||
<line x1="56" y1="632" x2="90" y2="632" stroke="#e0a800" stroke-width="6" stroke-linecap="round"/>
|
||||
<text x="100" y="637" font-size="14" fill="#333" text-anchor="start">jaune = données 3,3 V</text>
|
||||
<text x="56" y="670" font-size="13" fill="#555" text-anchor="start">Tous les GND sont reliés :</text>
|
||||
<text x="56" y="688" font-size="13" fill="#555" text-anchor="start">Pi ↔ puce ↔ fil blanc ↔ matrice</text>
|
||||
<text x="56" y="706" font-size="13" fill="#555" text-anchor="start">↔ fil noir ↔ borne − de l'alim.</text>
|
||||
<text x="56" y="730" font-size="13" fill="#b00020" text-anchor="start" font-weight="bold">Alim. réglée sur 5,0 V maxi !</text>
|
||||
<text x="56" y="752" font-size="12" fill="#777" text-anchor="start">Un pont = les fils se croisent</text>
|
||||
<text x="56" y="766" font-size="12" fill="#777" text-anchor="start">sans se toucher.</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 17 KiB |
@@ -0,0 +1,62 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="820" height="470" viewBox="0 0 820 470" font-family="Helvetica, Arial, sans-serif">
|
||||
<title>Brochage du 74AHCT125 (vu de dessus)</title>
|
||||
<defs><marker id="fleche" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="7" markerHeight="7" orient="auto"><path d="M1 1 L9 5 L1 9 Z" fill="context-stroke"/></marker><marker id="flecheg" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="6" markerHeight="6" orient="auto"><path d="M1 1 L9 5 L1 9 Z" fill="#555"/></marker><marker id="flechec" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="7" markerHeight="7" orient="auto"><path d="M1 1 L9 5 L1 9 Z" fill="#cfcfcf"/></marker></defs>
|
||||
<rect width="820" height="470" fill="#ffffff"/>
|
||||
<text x="410" y="36" font-size="20" fill="#222" text-anchor="middle" font-weight="bold">74AHCT125 vu de DESSUS (encoche à gauche)</text>
|
||||
<rect x="250" y="150" width="320" height="110" rx="6" fill="#222"/>
|
||||
<path d="M250 185 a20 20 0 0 1 0 40" fill="#555"/>
|
||||
<circle cx="275" cy="240" r="6" fill="#555"/>
|
||||
<text x="410" y="212" font-size="24" fill="#eee" text-anchor="middle" font-weight="bold">74AHCT125</text>
|
||||
<rect x="272" y="260" width="16" height="26" rx="0" fill="#b8b8b8"/>
|
||||
<text x="280" y="252" font-size="12" fill="#bbb" text-anchor="middle">1</text>
|
||||
<text x="280" y="306" font-size="14" fill="#333" text-anchor="middle" font-weight="bold">1OE</text>
|
||||
<line x1="280" y1="312" x2="280" y2="325" stroke="#333" stroke-width="1.5"/>
|
||||
<text x="280" y="338" font-size="13" fill="#333" text-anchor="middle" font-weight="bold">→ GND</text>
|
||||
<rect x="315" y="260" width="16" height="26" rx="0" fill="#b8b8b8"/>
|
||||
<text x="323" y="252" font-size="12" fill="#bbb" text-anchor="middle">2</text>
|
||||
<text x="323" y="306" font-size="14" fill="#e0a800" text-anchor="middle" font-weight="bold">1A</text>
|
||||
<line x1="323" y1="312" x2="323" y2="347" stroke="#e0a800" stroke-width="1.5"/>
|
||||
<text x="323" y="360" font-size="13" fill="#e0a800" text-anchor="middle" font-weight="bold">← GPIO 18</text>
|
||||
<rect x="358" y="260" width="16" height="26" rx="0" fill="#b8b8b8"/>
|
||||
<text x="366" y="252" font-size="12" fill="#bbb" text-anchor="middle">3</text>
|
||||
<text x="366" y="306" font-size="14" fill="#2a9d3a" text-anchor="middle" font-weight="bold">1Y</text>
|
||||
<line x1="366" y1="312" x2="366" y2="369" stroke="#2a9d3a" stroke-width="1.5"/>
|
||||
<text x="366" y="382" font-size="13" fill="#2a9d3a" text-anchor="middle" font-weight="bold">→ 330 Ω → DIN</text>
|
||||
<rect x="401" y="260" width="16" height="26" rx="0" fill="#b8b8b8"/>
|
||||
<text x="409" y="252" font-size="12" fill="#bbb" text-anchor="middle">4</text>
|
||||
<text x="409" y="306" font-size="14" fill="#aaa" text-anchor="middle" font-weight="bold">2OE</text>
|
||||
<rect x="444" y="260" width="16" height="26" rx="0" fill="#b8b8b8"/>
|
||||
<text x="452" y="252" font-size="12" fill="#bbb" text-anchor="middle">5</text>
|
||||
<text x="452" y="306" font-size="14" fill="#aaa" text-anchor="middle" font-weight="bold">2A</text>
|
||||
<rect x="487" y="260" width="16" height="26" rx="0" fill="#b8b8b8"/>
|
||||
<text x="495" y="252" font-size="12" fill="#bbb" text-anchor="middle">6</text>
|
||||
<text x="495" y="306" font-size="14" fill="#aaa" text-anchor="middle" font-weight="bold">2Y</text>
|
||||
<rect x="530" y="260" width="16" height="26" rx="0" fill="#b8b8b8"/>
|
||||
<text x="538" y="252" font-size="12" fill="#bbb" text-anchor="middle">7</text>
|
||||
<text x="538" y="306" font-size="14" fill="#333" text-anchor="middle" font-weight="bold">GND</text>
|
||||
<line x1="538" y1="312" x2="538" y2="325" stroke="#333" stroke-width="1.5"/>
|
||||
<text x="538" y="338" font-size="13" fill="#333" text-anchor="middle" font-weight="bold">→ GND</text>
|
||||
<rect x="272" y="124" width="16" height="26" rx="0" fill="#b8b8b8"/>
|
||||
<text x="280" y="168" font-size="12" fill="#bbb" text-anchor="middle">14</text>
|
||||
<text x="280" y="114" font-size="14" fill="#d62828" text-anchor="middle" font-weight="bold">VCC</text>
|
||||
<text x="280" y="92" font-size="13" fill="#d62828" text-anchor="middle" font-weight="bold">← 5 V</text>
|
||||
<rect x="315" y="124" width="16" height="26" rx="0" fill="#b8b8b8"/>
|
||||
<text x="323" y="168" font-size="12" fill="#bbb" text-anchor="middle">13</text>
|
||||
<text x="323" y="114" font-size="14" fill="#aaa" text-anchor="middle" font-weight="bold">4OE</text>
|
||||
<rect x="358" y="124" width="16" height="26" rx="0" fill="#b8b8b8"/>
|
||||
<text x="366" y="168" font-size="12" fill="#bbb" text-anchor="middle">12</text>
|
||||
<text x="366" y="114" font-size="14" fill="#aaa" text-anchor="middle" font-weight="bold">4A</text>
|
||||
<rect x="401" y="124" width="16" height="26" rx="0" fill="#b8b8b8"/>
|
||||
<text x="409" y="168" font-size="12" fill="#bbb" text-anchor="middle">11</text>
|
||||
<text x="409" y="114" font-size="14" fill="#aaa" text-anchor="middle" font-weight="bold">4Y</text>
|
||||
<rect x="444" y="124" width="16" height="26" rx="0" fill="#b8b8b8"/>
|
||||
<text x="452" y="168" font-size="12" fill="#bbb" text-anchor="middle">10</text>
|
||||
<text x="452" y="114" font-size="14" fill="#aaa" text-anchor="middle" font-weight="bold">3OE</text>
|
||||
<rect x="487" y="124" width="16" height="26" rx="0" fill="#b8b8b8"/>
|
||||
<text x="495" y="168" font-size="12" fill="#bbb" text-anchor="middle">9</text>
|
||||
<text x="495" y="114" font-size="14" fill="#aaa" text-anchor="middle" font-weight="bold">3A</text>
|
||||
<rect x="530" y="124" width="16" height="26" rx="0" fill="#b8b8b8"/>
|
||||
<text x="538" y="168" font-size="12" fill="#bbb" text-anchor="middle">8</text>
|
||||
<text x="538" y="114" font-size="14" fill="#aaa" text-anchor="middle" font-weight="bold">3Y</text>
|
||||
<text x="40" y="445" font-size="13" fill="#555" text-anchor="start">On n'utilise qu'une des 4 « portes » (la n° 1). Pattes en gris : non branchées (on peut relier 4, 10 et 13 au 5 V pour les désactiver).</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.7 KiB |
@@ -0,0 +1,55 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="330" viewBox="0 0 1200 330" font-family="Helvetica, Arial, sans-serif">
|
||||
<title>De Krita à la matrice : le trajet d'une animation</title>
|
||||
<defs><marker id="fleche" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="7" markerHeight="7" orient="auto"><path d="M1 1 L9 5 L1 9 Z" fill="context-stroke"/></marker><marker id="flecheg" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="6" markerHeight="6" orient="auto"><path d="M1 1 L9 5 L1 9 Z" fill="#555"/></marker><marker id="flechec" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="7" markerHeight="7" orient="auto"><path d="M1 1 L9 5 L1 9 Z" fill="#cfcfcf"/></marker></defs>
|
||||
<rect width="1200" height="330" fill="#ffffff"/>
|
||||
<rect x="30" y="60" width="170" height="190" rx="12" fill="#ffffff" stroke="#7b3fa0" stroke-width="3"/>
|
||||
<rect x="30" y="60" width="170" height="46" rx="12" fill="#7b3fa0"/>
|
||||
<rect x="30" y="90" width="170" height="16" rx="0" fill="#7b3fa0"/>
|
||||
<text x="115.0" y="90" font-size="18" fill="#fff" text-anchor="middle" font-weight="bold">1. Dessiner</text>
|
||||
<text x="115.0" y="140" font-size="14" fill="#222" text-anchor="middle" font-weight="bold">Krita</text>
|
||||
<text x="115.0" y="166" font-size="13" fill="#555" text-anchor="middle">document 32 × 8 px</text>
|
||||
<text x="115.0" y="192" font-size="13" fill="#555" text-anchor="middle">pixel art, sans flou</text>
|
||||
<line x1="204" y1="155" x2="224" y2="155" stroke="#555" stroke-width="3" marker-end="url(#flecheg)"/>
|
||||
<rect x="228" y="60" width="170" height="190" rx="12" fill="#ffffff" stroke="#b5418f" stroke-width="3"/>
|
||||
<rect x="228" y="60" width="170" height="46" rx="12" fill="#b5418f"/>
|
||||
<rect x="228" y="90" width="170" height="16" rx="0" fill="#b5418f"/>
|
||||
<text x="313.0" y="90" font-size="18" fill="#fff" text-anchor="middle" font-weight="bold">2. Exporter</text>
|
||||
<text x="313.0" y="140" font-size="14" fill="#222" text-anchor="middle" font-weight="bold">GIF animé</text>
|
||||
<text x="313.0" y="166" font-size="13" fill="#555" text-anchor="middle">ou séquence de PNG</text>
|
||||
<text x="313.0" y="192" font-size="13" fill="#555" text-anchor="middle">(Rendu de l'animation)</text>
|
||||
<line x1="402" y1="155" x2="422" y2="155" stroke="#555" stroke-width="3" marker-end="url(#flecheg)"/>
|
||||
<rect x="426" y="60" width="170" height="190" rx="12" fill="#ffffff" stroke="#c9682a" stroke-width="3"/>
|
||||
<rect x="426" y="60" width="170" height="46" rx="12" fill="#c9682a"/>
|
||||
<rect x="426" y="90" width="170" height="16" rx="0" fill="#c9682a"/>
|
||||
<text x="511.0" y="90" font-size="18" fill="#fff" text-anchor="middle" font-weight="bold">3. Vérifier</text>
|
||||
<text x="511.0" y="140" font-size="14" fill="#222" text-anchor="middle" font-weight="bold">verifier_animation.py</text>
|
||||
<text x="511.0" y="166" font-size="13" fill="#555" text-anchor="middle">+ simulateur</text>
|
||||
<text x="511.0" y="192" font-size="13" fill="#555" text-anchor="middle">sur l'ordinateur</text>
|
||||
<line x1="600" y1="155" x2="620" y2="155" stroke="#555" stroke-width="3" marker-end="url(#flecheg)"/>
|
||||
<rect x="624" y="60" width="170" height="190" rx="12" fill="#ffffff" stroke="#b08900" stroke-width="3"/>
|
||||
<rect x="624" y="60" width="170" height="46" rx="12" fill="#b08900"/>
|
||||
<rect x="624" y="90" width="170" height="16" rx="0" fill="#b08900"/>
|
||||
<text x="709.0" y="90" font-size="18" fill="#fff" text-anchor="middle" font-weight="bold">4. Déposer</text>
|
||||
<text x="709.0" y="140" font-size="14" fill="#222" text-anchor="middle" font-weight="bold">dossier « animations »</text>
|
||||
<text x="709.0" y="166" font-size="13" fill="#555" text-anchor="middle">carte SD ou réseau</text>
|
||||
<text x="709.0" y="192" font-size="13" fill="#555" text-anchor="middle">(01_, 02_… = ordre)</text>
|
||||
<line x1="798" y1="155" x2="818" y2="155" stroke="#555" stroke-width="3" marker-end="url(#flecheg)"/>
|
||||
<rect x="822" y="60" width="170" height="190" rx="12" fill="#ffffff" stroke="#1f7a3a" stroke-width="3"/>
|
||||
<rect x="822" y="60" width="170" height="46" rx="12" fill="#1f7a3a"/>
|
||||
<rect x="822" y="90" width="170" height="16" rx="0" fill="#1f7a3a"/>
|
||||
<text x="907.0" y="90" font-size="18" fill="#fff" text-anchor="middle" font-weight="bold">5. Lire</text>
|
||||
<text x="907.0" y="140" font-size="14" fill="#222" text-anchor="middle" font-weight="bold">Raspberry Pi</text>
|
||||
<text x="907.0" y="166" font-size="13" fill="#555" text-anchor="middle">lecteur.py : gamma,</text>
|
||||
<text x="907.0" y="192" font-size="13" fill="#555" text-anchor="middle">luminosité, serpentin</text>
|
||||
<line x1="996" y1="155" x2="1016" y2="155" stroke="#555" stroke-width="3" marker-end="url(#flecheg)"/>
|
||||
<rect x="1020" y="60" width="170" height="190" rx="12" fill="#ffffff" stroke="#2f6fc4" stroke-width="3"/>
|
||||
<rect x="1020" y="60" width="170" height="46" rx="12" fill="#2f6fc4"/>
|
||||
<rect x="1020" y="90" width="170" height="16" rx="0" fill="#2f6fc4"/>
|
||||
<text x="1105.0" y="90" font-size="18" fill="#fff" text-anchor="middle" font-weight="bold">6. Afficher</text>
|
||||
<text x="1105.0" y="140" font-size="14" fill="#222" text-anchor="middle" font-weight="bold">74AHCT125 (3,3 → 5 V)</text>
|
||||
<text x="1105.0" y="166" font-size="13" fill="#555" text-anchor="middle">puis 256 LED</text>
|
||||
<text x="1105.0" y="192" font-size="13" fill="#555" text-anchor="middle">WS2812B</text>
|
||||
<text x="30" y="36" font-size="22" fill="#222" text-anchor="start" font-weight="bold">De Krita à la matrice</text>
|
||||
<path d="M115 270 V292 H1003 V270" stroke="#aaa" stroke-width="2" fill="none" stroke-dasharray="6 5"/>
|
||||
<text x="559" y="316" font-size="14" fill="#555" text-anchor="middle">Étapes 1 à 4 : l'équipe design, sur n'importe quel ordinateur. Étapes 5 et 6 : automatiques, dans la vitrine.</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.6 KiB |
@@ -0,0 +1,85 @@
|
||||
"""
|
||||
=============================================================================
|
||||
TEST DE LA MATRICE — branchement, orientation, alimentation
|
||||
Workshop « Vitrine pixels »
|
||||
=============================================================================
|
||||
|
||||
À lancer EN PREMIER, après chaque nouveau montage :
|
||||
sudo ~/vitrine-env/bin/python test_matrice.py
|
||||
|
||||
(ou, pour voir à quoi ça ressemble sans matériel :
|
||||
python3 test_matrice.py --simulateur)
|
||||
|
||||
Trois tests s'enchaînent, puis tout s'éteint :
|
||||
|
||||
1. LES COINS : un pixel de couleur dans chaque coin, 5 secondes.
|
||||
ROUGE en haut à gauche VERT en haut à droite
|
||||
BLEU en bas à gauche BLANC en bas à droite
|
||||
Sinon : changez INVERSER_X / INVERSER_Y dans matrice.py.
|
||||
|
||||
2. L'ORDRE DE LA CHAÎNE : un point orange parcourt les 256 LED
|
||||
dans l'ordre où le signal les traverse. On voit le serpentin.
|
||||
Si le point s'arrête en route : une LED ou une soudure est abîmée
|
||||
à cet endroit (tout ce qui suit reste éteint).
|
||||
|
||||
3. LES COULEURS PLEINES : tout en rouge, puis vert, puis bleu, puis
|
||||
blanc. Vérifie l'ordre des couleurs (GRB) et l'alimentation.
|
||||
Le blanc est le plus gourmand : si la matrice devient orangée ou
|
||||
clignote, l'alimentation est à sa limite (voir README, partie 10).
|
||||
"""
|
||||
|
||||
import matrice
|
||||
from matrice import LARGEUR, HAUTEUR, NB_LEDS, ROUGE, VERT, BLEU, BLANC, ORANGE
|
||||
|
||||
|
||||
def test_coins():
|
||||
print("1. Test des coins : rouge ↖ vert ↗ bleu ↙ blanc ↘")
|
||||
matrice.effacer()
|
||||
matrice.pixel(0, 0, ROUGE)
|
||||
matrice.pixel(LARGEUR - 1, 0, VERT)
|
||||
matrice.pixel(0, HAUTEUR - 1, BLEU)
|
||||
matrice.pixel(LARGEUR - 1, HAUTEUR - 1, BLANC)
|
||||
matrice.montrer()
|
||||
matrice.attendre(5000)
|
||||
|
||||
|
||||
def test_ordre_des_leds():
|
||||
print("2. Ordre de la chaîne : le point orange suit le serpentin")
|
||||
# Pour suivre l'ordre de la CHAÎNE (et pas l'ordre des coordonnées),
|
||||
# on cherche, pour chaque numéro de LED, la position (x, y) qui lui
|
||||
# correspond grâce à XY().
|
||||
position = {}
|
||||
for x in range(LARGEUR):
|
||||
for y in range(HAUTEUR):
|
||||
position[matrice.XY(x, y)] = (x, y)
|
||||
|
||||
matrice.effacer()
|
||||
for numero in range(NB_LEDS):
|
||||
x, y = position[numero]
|
||||
matrice.pixel(x, y, ORANGE) # la tête du serpent
|
||||
matrice.montrer()
|
||||
matrice.pixel(x, y, (60, 20, 0)) # laisse une trace plus sombre
|
||||
matrice.attendre(15)
|
||||
matrice.attendre(1000)
|
||||
|
||||
|
||||
def test_couleurs_pleines():
|
||||
print("3. Couleurs pleines : rouge, vert, bleu, blanc")
|
||||
for nom, couleur in [("rouge", ROUGE), ("vert", VERT), ("bleu", BLEU), ("blanc", BLANC)]:
|
||||
print(" tout en", nom)
|
||||
matrice.effacer(couleur)
|
||||
matrice.montrer()
|
||||
matrice.attendre(2000)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
matrice.demarrer()
|
||||
try:
|
||||
test_coins()
|
||||
test_ordre_des_leds()
|
||||
test_couleurs_pleines()
|
||||
print("Terminé.")
|
||||
except KeyboardInterrupt:
|
||||
print("\nArrêt demandé.")
|
||||
finally:
|
||||
matrice.eteindre()
|
||||