8x32 PI : séquences jusqu'à 120 PNG, test du fil de données, montage A validé
- serveur_web.py : séquences limitées à 120 PNG (message clair au-delà), envois jusqu'à 200 Mo, fichiers temporaires sur la carte SD (et plus dans /run, trop petit), garde-fou si une séquence est envoyée dans le cadre « fichiers séparés ». - outils/test_fil_donnees.py : met GPIO 18 à 1 puis à 0 toutes les 10 s, pour suivre le signal au multimètre du Pi jusqu'à DIN. - README : montage A (Pi en direct, LED en 4,4 V) testé et validé ; montage B (transistor 2N3904) déconseillé, trop lent (temps de stockage ≈ 0,2 µs face à un « 0 » de 0,4 µs) ; mise en garde sur les convertisseurs HV/LV ; dépannage « seule la 1re LED s'allume ». - INTERFACE_WEB.md, CLAUDE.md : nouvelles limites, accès au Pi en Wi-Fi seul, lecteur activé au démarrage. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -83,7 +83,11 @@ Pour un export Krita en **séquence d'images** (`feu_0000.png`,
|
|||||||
3. **Envoyer**.
|
3. **Envoyer**.
|
||||||
|
|
||||||
Les images sont mises dans l'ordre de leurs noms : gardez la numérotation de
|
Les images sont mises dans l'ordre de leurs noms : gardez la numérotation de
|
||||||
Krita.
|
Krita. **120 images au maximum** par séquence.
|
||||||
|
|
||||||
|
> ⚠️ N'envoyez pas une séquence dans le cadre « Un ou plusieurs fichiers » :
|
||||||
|
> chaque PNG deviendrait une animation séparée. Au-delà de 10 PNG envoyés
|
||||||
|
> d'un coup, la page refuse et vous redirige vers le bon cadre.
|
||||||
|
|
||||||
### Vérifications automatiques
|
### Vérifications automatiques
|
||||||
|
|
||||||
@@ -138,7 +142,9 @@ qu'on ne clique pas sur **Vider**. Vider la corbeille efface **définitivement**
|
|||||||
rien modifier et affiche une erreur. Pendant l'exposition, on peut choisir :
|
rien modifier et affiche une erreur. Pendant l'exposition, on peut choisir :
|
||||||
protection totale (et la page sert seulement à regarder et à jouer), ou
|
protection totale (et la page sert seulement à regarder et à jouer), ou
|
||||||
partition de démarrage modifiable (et la page reste pleinement utilisable).
|
partition de démarrage modifiable (et la page reste pleinement utilisable).
|
||||||
- **Taille maximum** d'un envoi : 50 Mo (largement assez : un GIF de 8 × 32
|
- **Une séquence de PNG** compte **120 images au maximum** (10 secondes à
|
||||||
|
12 images/s). Plus long : découpez-la, ou exportez-la en GIF.
|
||||||
|
- **Taille maximum** d'un envoi : 200 Mo (largement assez : un GIF de 8 × 32
|
||||||
pèse quelques kilo-octets).
|
pèse quelques kilo-octets).
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -198,7 +204,8 @@ Les réglages sont en haut de [`serveur_web.py`](serveur_web.py) :
|
|||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `MOT_DE_PASSE` | `None` | ex. `"pixels"` : le navigateur demande un nom (n'importe lequel) et ce mot de passe |
|
| `MOT_DE_PASSE` | `None` | ex. `"pixels"` : le navigateur demande un nom (n'importe lequel) et ce mot de passe |
|
||||||
| `PORT` | `80` | le port web ; avec un autre, l'adresse devient `http://172.16.50.27:8080` |
|
| `PORT` | `80` | le port web ; avec un autre, l'adresse devient `http://172.16.50.27:8080` |
|
||||||
| `TAILLE_MAX_MO` | `50` | taille maximum d'un envoi |
|
| `TAILLE_MAX_MO` | `200` | taille maximum d'un envoi |
|
||||||
|
| `MAX_IMAGES_SEQUENCE` | `120` | nombre maximum d'images d'une séquence de PNG |
|
||||||
|
|
||||||
Après une modification : `sudo systemctl restart vitrine-web`.
|
Après une modification : `sudo systemctl restart vitrine-web`.
|
||||||
|
|
||||||
|
|||||||
+27
-5
@@ -160,6 +160,7 @@ inverse des aiguilles d'une montre : 1 à 7 en bas, de gauche à droite, puis
|
|||||||
### Pas de 74AHCT125 sous la main ? Deux solutions de dépannage
|
### Pas de 74AHCT125 sous la main ? Deux solutions de dépannage
|
||||||
|
|
||||||
**Solution A : baisser la tension de la matrice (conseillée, aucun composant).**
|
**Solution A : baisser la tension de la matrice (conseillée, aucun composant).**
|
||||||
|
✅ *Testée et validée sur le Pi 3 du workshop (octobre 2026).*
|
||||||
Le seuil du « 1 » vaut 70 % de la tension d'alimentation **des LED**. Si on
|
Le seuil du « 1 » vaut 70 % de la tension d'alimentation **des LED**. Si on
|
||||||
alimente la matrice en **4,4 V** au lieu de 5 V, le seuil tombe à
|
alimente la matrice en **4,4 V** au lieu de 5 V, le seuil tombe à
|
||||||
0,7 × 4,4 ≈ **3,1 V**, et les 3,3 V du Pi suffisent. Les WS2812B fonctionnent
|
0,7 × 4,4 ≈ **3,1 V**, et les 3,3 V du Pi suffisent. Les WS2812B fonctionnent
|
||||||
@@ -184,7 +185,10 @@ En résumé :
|
|||||||
matrice, et ne doit **jamais** toucher une patte du Pi.
|
matrice, et ne doit **jamais** toucher une patte du Pi.
|
||||||
- Gardez le fil de données **court** (moins de 30 cm).
|
- Gardez le fil de données **court** (moins de 30 cm).
|
||||||
|
|
||||||
**Solution B : un transistor « inverseur » (si la solution A scintille).**
|
**Solution B : un transistor « inverseur » (déconseillée).**
|
||||||
|
❌ *Essayée pendant le workshop : la matrice ne s'allume pas, seule la 1re LED
|
||||||
|
prend une couleur au hasard.* On la décrit quand même, pour comprendre
|
||||||
|
pourquoi elle échoue.
|
||||||
Avec un transistor NPN du kit, de préférence un **2N3904** (le plus rapide du
|
Avec un transistor NPN du kit, de préférence un **2N3904** (le plus rapide du
|
||||||
kit ; un BC337 ou un S8050 peuvent convenir), on fabrique un adaptateur 5 V.
|
kit ; un BC337 ou un S8050 peuvent convenir), on fabrique un adaptateur 5 V.
|
||||||
Le transistor **inverse** le signal (un 1 devient un 0) : il faut le dire au
|
Le transistor **inverse** le signal (un 1 devient un 0) : il faut le dire au
|
||||||
@@ -209,8 +213,25 @@ Le même montage, en schéma électrique :
|
|||||||
```
|
```
|
||||||
|
|
||||||
Ici l'alimentation d'établi reste à **5,0 V**. Le 5 V n'atteint jamais le Pi :
|
Ici l'alimentation d'établi reste à **5,0 V**. Le 5 V n'atteint jamais le Pi :
|
||||||
il s'arrête au collecteur du transistor. Ce montage est plus lent qu'un
|
il s'arrête au collecteur du transistor.
|
||||||
74AHCT125 : il marche souvent bien, mais c'est un dépannage.
|
|
||||||
|
> **Pourquoi ça ne marche pas : le transistor est trop lent.** Les LED lisent
|
||||||
|
> des impulsions très courtes : un « 0 » est une impulsion de **0,4 µs**
|
||||||
|
> (0,000 000 4 s), un « 1 » une impulsion de 0,8 µs. Or un transistor comme
|
||||||
|
> le 2N3904, quand il conduit à fond (« saturé »), met environ **0,2 µs** à se
|
||||||
|
> bloquer : c'est son *temps de stockage*. Chaque impulsion est raccourcie
|
||||||
|
> d'autant, et la résistance de tirage arrondit encore les fronts. Le « 0 »
|
||||||
|
> tombe sous ce que la LED accepte : elle reçoit du bruit, la première LED
|
||||||
|
> affiche une couleur au hasard et ne transmet rien aux suivantes.
|
||||||
|
> Un 74AHCT125 réagit en moins de 0,01 µs : 20 fois plus vite.
|
||||||
|
>
|
||||||
|
> On pourrait l'accélérer (condensateur de 100 pF en parallèle de la
|
||||||
|
> résistance de base, résistance de tirage plus faible), mais le résultat
|
||||||
|
> resterait fragile. **Préférez la solution A, ou la puce.**
|
||||||
|
|
||||||
|
> **Même problème avec les petits modules « convertisseurs de niveau »**
|
||||||
|
> bidirectionnels (marqués HV / LV, à transistors BSS138) : ils sont faits
|
||||||
|
> pour des signaux lents (I²C), pas pour les 800 000 bits par seconde des LED.
|
||||||
|
|
||||||
> Dans les deux cas, le reste (logiciel, installation, Krita) ne change pas.
|
> Dans les deux cas, le reste (logiciel, installation, Krita) ne change pas.
|
||||||
|
|
||||||
@@ -532,7 +553,8 @@ En haut de [`serveur_web.py`](serveur_web.py) :
|
|||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `MOT_DE_PASSE` | `None` | ⚠️ sans mot de passe, **toute personne sur le réseau** peut ajouter ou supprimer des animations. Ex. `"pixels"` : le navigateur le demandera |
|
| `MOT_DE_PASSE` | `None` | ⚠️ sans mot de passe, **toute personne sur le réseau** peut ajouter ou supprimer des animations. Ex. `"pixels"` : le navigateur le demandera |
|
||||||
| `PORT` | `80` | le port web habituel ; un autre port s'ajoute à l'adresse (`http://172.16.50.27:8080`) |
|
| `PORT` | `80` | le port web habituel ; un autre port s'ajoute à l'adresse (`http://172.16.50.27:8080`) |
|
||||||
| `TAILLE_MAX_MO` | `50` | taille maximum d'un envoi |
|
| `TAILLE_MAX_MO` | `200` | taille maximum d'un envoi (tous les fichiers ensemble) |
|
||||||
|
| `MAX_IMAGES_SEQUENCE` | `120` | nombre maximum d'images dans une séquence de PNG |
|
||||||
|
|
||||||
> **Avec la protection contre les coupures de courant** (overlay, voir
|
> **Avec la protection contre les coupures de courant** (overlay, voir
|
||||||
> [MODE_VITRINE.md](MODE_VITRINE.md#4-survivre-aux-coupures-de-courant)), si la
|
> [MODE_VITRINE.md](MODE_VITRINE.md#4-survivre-aux-coupures-de-courant)), si la
|
||||||
@@ -563,7 +585,7 @@ Le mode d'emploi détaillé, pour l'équipe design, est dans
|
|||||||
| **Ç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 |
|
| **Ç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 |
|
| **Couleurs aléatoires, scintillement** | Puce absente ou de type HC (sans T) | Vérifier les lettres sur la puce |
|
||||||
| | Montage sans puce (solution A) | Baisser encore un peu l'alimentation (4,2 V), raccourcir le fil vert, ou passer à la solution B |
|
| | Montage sans puce (solution A) | Baisser encore un peu l'alimentation (4,2 V), raccourcir le fil vert, ou passer à la solution B |
|
||||||
| **Rien ne s'allume avec le transistor** (solution B) | `INVERSER_SIGNAL` oublié, ou transistor à l'envers | `INVERSER_SIGNAL = True` dans `matrice.py` ; vérifier E-B-C |
|
| **Seule la 1re LED s'allume, d'une couleur au hasard** | Signal déformé : convertisseur ou transistor trop lent (solution B), masse non commune | Passer à la solution A (sans convertisseur, LED en 4,4 V), vérifier le fil blanc |
|
||||||
| | Masse mal serrée | Revérifier toutes les masses |
|
| | 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` |
|
| **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) |
|
| **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) |
|
||||||
|
|||||||
@@ -0,0 +1,86 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
=============================================================================
|
||||||
|
TEST DU FIL DE DONNÉES AU MULTIMÈTRE
|
||||||
|
Workshop « Vitrine pixels »
|
||||||
|
=============================================================================
|
||||||
|
|
||||||
|
Quand la matrice ne s'allume pas du tout, il faut savoir OÙ le signal se
|
||||||
|
perd : à la sortie du Pi ? dans le convertisseur de niveau ? dans un fil ?
|
||||||
|
|
||||||
|
Le vrai signal des LED est beaucoup trop rapide pour un multimètre
|
||||||
|
(800 000 bits par seconde). Ce programme transforme donc la patte 12
|
||||||
|
(GPIO 18) en simple interrupteur LENT :
|
||||||
|
|
||||||
|
10 secondes à « 1 » (3,3 V à la sortie du Pi)
|
||||||
|
10 secondes à « 0 » (0 V)
|
||||||
|
… et on recommence, pendant 2 minutes (Ctrl + C pour arrêter).
|
||||||
|
|
||||||
|
Pendant ce temps, on mesure au multimètre (calibre 20 V continu, pointe
|
||||||
|
NOIRE sur un GND, pointe ROUGE sur le point à tester) :
|
||||||
|
|
||||||
|
Point mesuré pendant « 1 » pendant « 0 »
|
||||||
|
------------------------------------------- ------------- -------------
|
||||||
|
1. patte 12 du Pi (GPIO 18) ≈ 3,3 V 0 V
|
||||||
|
2. entrée du convertisseur (côté 3,3 V) ≈ 3,3 V 0 V
|
||||||
|
3. sortie du convertisseur (côté 5 V) ≈ 5 V 0 V (*)
|
||||||
|
4. après la résistance de 330 Ω ≈ 5 V 0 V (*)
|
||||||
|
5. fil VERT de la fiche, au plus près de DIN ≈ 5 V 0 V (*)
|
||||||
|
|
||||||
|
(*) Avec le montage A (sans convertisseur, matrice en 4,4 V) : 3,3 V.
|
||||||
|
Avec le montage B (transistor) : INVERSÉ, ≈ 5 V pendant « 0 » et
|
||||||
|
0 V pendant « 1 ». C'est normal.
|
||||||
|
|
||||||
|
Le premier point où la mesure est fausse indique la panne : le problème se
|
||||||
|
trouve entre ce point et le précédent.
|
||||||
|
|
||||||
|
Et aussi, sans rien lancer :
|
||||||
|
- entre le 5V et le GND des fils du MILIEU de la matrice : 5,0 V (ou 4,4 V) ;
|
||||||
|
- entre le GND du Pi et le GND de la matrice : 0 V (sinon, masse pas commune).
|
||||||
|
|
||||||
|
Lancer (le lecteur ne doit pas tourner en même temps) :
|
||||||
|
sudo systemctl stop vitrine-pixels
|
||||||
|
sudo ~/vitrine-env/bin/python outils/test_fil_donnees.py
|
||||||
|
|
||||||
|
Le prochain programme qui affiche sur la matrice remettra tout seul la
|
||||||
|
patte 12 dans son mode normal.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
|
||||||
|
BROCHE = "18"
|
||||||
|
DUREE_ETAT = 10 # secondes à « 1 », puis à « 0 »
|
||||||
|
DUREE_TOTALE = 120 # secondes
|
||||||
|
|
||||||
|
|
||||||
|
def regler(niveau):
|
||||||
|
"""Met la patte en sortie, à 1 (dh = « drive high ») ou à 0 (dl)."""
|
||||||
|
# « raspi-gpio » sur Raspbian 11, « pinctrl » sur les systèmes plus récents.
|
||||||
|
outil = "raspi-gpio" if shutil.which("raspi-gpio") else "pinctrl"
|
||||||
|
subprocess.run([outil, "set", BROCHE, "op", "dh" if niveau else "dl"], check=True)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
print(__doc__.split("Lancer")[0])
|
||||||
|
debut = time.monotonic()
|
||||||
|
niveau = 1
|
||||||
|
try:
|
||||||
|
while time.monotonic() - debut < DUREE_TOTALE:
|
||||||
|
regler(niveau)
|
||||||
|
print(f"{time.strftime('%H:%M:%S')} patte 12 à « {niveau} » "
|
||||||
|
f"({'≈ 3,3 V' if niveau else '0 V'} à la sortie du Pi)")
|
||||||
|
sys.stdout.flush()
|
||||||
|
time.sleep(DUREE_ETAT)
|
||||||
|
niveau = 1 - niveau
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
pass
|
||||||
|
finally:
|
||||||
|
regler(0)
|
||||||
|
print("Terminé : patte 12 à 0.")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
+29
-4
@@ -85,8 +85,21 @@ PORT = 80
|
|||||||
# d'utilisateur, n'importe lequel, et ce mot de passe).
|
# d'utilisateur, n'importe lequel, et ce mot de passe).
|
||||||
MOT_DE_PASSE = None
|
MOT_DE_PASSE = None
|
||||||
|
|
||||||
# Taille maximum d'un envoi, en mégaoctets.
|
# Taille maximum d'un envoi, en mégaoctets (tous les fichiers ensemble).
|
||||||
TAILLE_MAX_MO = 50
|
TAILLE_MAX_MO = 200
|
||||||
|
|
||||||
|
# Nombre maximum d'images dans une SÉQUENCE de PNG (120 images = 10 secondes
|
||||||
|
# à 12 images/s, 5 secondes à 24 images/s).
|
||||||
|
MAX_IMAGES_SEQUENCE = 120
|
||||||
|
|
||||||
|
# Au-delà de ce nombre de PNG envoyés d'un coup dans le cadre « Un ou
|
||||||
|
# plusieurs fichiers », c'est sûrement une séquence envoyée au mauvais
|
||||||
|
# endroit (chaque PNG deviendrait une animation) : on refuse et on explique.
|
||||||
|
MAX_PNG_SEPARES = 10
|
||||||
|
|
||||||
|
# Où ranger les envois le temps de les vérifier : le dossier temporaire du
|
||||||
|
# système (sur la carte SD), pas /run qui est en mémoire vive et trop petit.
|
||||||
|
DOSSIER_ENVOIS = tempfile.gettempdir()
|
||||||
|
|
||||||
# Sous-dossiers spéciaux du dossier des animations. Leur nom commence par
|
# Sous-dossiers spéciaux du dossier des animations. Leur nom commence par
|
||||||
# un point : le lecteur les ignore (voir lecteur.est_cache).
|
# un point : le lecteur les ignore (voir lecteur.est_cache).
|
||||||
@@ -344,6 +357,7 @@ def accueil():
|
|||||||
else ":" + request.host.rsplit(":", 1)[1],
|
else ":" + request.host.rsplit(":", 1)[1],
|
||||||
extensions=",".join(lecteur.EXTENSIONS),
|
extensions=",".join(lecteur.EXTENSIONS),
|
||||||
taille_max=TAILLE_MAX_MO,
|
taille_max=TAILLE_MAX_MO,
|
||||||
|
max_images=MAX_IMAGES_SEQUENCE,
|
||||||
largeur=LARGEUR, hauteur=HAUTEUR,
|
largeur=LARGEUR, hauteur=HAUTEUR,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -383,6 +397,12 @@ def ajouter():
|
|||||||
fichiers = [f for f in request.files.getlist("fichiers") if f.filename]
|
fichiers = [f for f in request.files.getlist("fichiers") if f.filename]
|
||||||
if not fichiers:
|
if not fichiers:
|
||||||
flash("Aucun fichier choisi.", "erreur")
|
flash("Aucun fichier choisi.", "erreur")
|
||||||
|
nb_png = sum(f.filename.lower().endswith(".png") for f in fichiers)
|
||||||
|
if nb_png > MAX_PNG_SEPARES:
|
||||||
|
flash(f"{nb_png} PNG envoyés d'un coup : est-ce une séquence d'images ? "
|
||||||
|
"Utilisez plutôt le cadre « Une séquence de PNG », sinon chaque image "
|
||||||
|
"deviendrait une animation séparée.", "erreur")
|
||||||
|
return redirect(url_for("accueil"))
|
||||||
for fichier in fichiers:
|
for fichier in fichiers:
|
||||||
base, extension = os.path.splitext(fichier.filename)
|
base, extension = os.path.splitext(fichier.filename)
|
||||||
extension = extension.lower()
|
extension = extension.lower()
|
||||||
@@ -391,7 +411,7 @@ def ajouter():
|
|||||||
f"(acceptés : {', '.join(lecteur.EXTENSIONS)}).", "erreur")
|
f"(acceptés : {', '.join(lecteur.EXTENSIONS)}).", "erreur")
|
||||||
continue
|
continue
|
||||||
# On enregistre d'abord dans un dossier temporaire, on vérifie, puis on range.
|
# On enregistre d'abord dans un dossier temporaire, on vérifie, puis on range.
|
||||||
descripteur, temporaire = tempfile.mkstemp(suffix=extension, dir=lecteur.DOSSIER_ETAT)
|
descripteur, temporaire = tempfile.mkstemp(suffix=extension, dir=DOSSIER_ENVOIS)
|
||||||
os.close(descripteur)
|
os.close(descripteur)
|
||||||
fichier.save(temporaire)
|
fichier.save(temporaire)
|
||||||
probleme = verifier_animation(temporaire)
|
probleme = verifier_animation(temporaire)
|
||||||
@@ -419,8 +439,13 @@ def ajouter_sequence():
|
|||||||
if len(fichiers) < 2:
|
if len(fichiers) < 2:
|
||||||
flash("Une séquence demande au moins 2 images PNG.", "erreur")
|
flash("Une séquence demande au moins 2 images PNG.", "erreur")
|
||||||
return redirect(url_for("accueil"))
|
return redirect(url_for("accueil"))
|
||||||
|
if len(fichiers) > MAX_IMAGES_SEQUENCE:
|
||||||
|
flash(f"{len(fichiers)} images : c'est trop, une séquence peut en compter "
|
||||||
|
f"{MAX_IMAGES_SEQUENCE} au maximum. Découpez-la en plusieurs "
|
||||||
|
"animations, ou exportez-la en GIF.", "erreur")
|
||||||
|
return redirect(url_for("accueil"))
|
||||||
|
|
||||||
temporaire = tempfile.mkdtemp(dir=lecteur.DOSSIER_ETAT)
|
temporaire = tempfile.mkdtemp(dir=DOSSIER_ENVOIS)
|
||||||
for i, fichier in enumerate(fichiers):
|
for i, fichier in enumerate(fichiers):
|
||||||
if not fichier.filename.lower().endswith(".png"):
|
if not fichier.filename.lower().endswith(".png"):
|
||||||
shutil.rmtree(temporaire)
|
shutil.rmtree(temporaire)
|
||||||
|
|||||||
@@ -57,7 +57,8 @@
|
|||||||
<form class="carte" method="post" action="{{ url_for('ajouter_sequence') }}" enctype="multipart/form-data">
|
<form class="carte" method="post" action="{{ url_for('ajouter_sequence') }}" enctype="multipart/form-data">
|
||||||
<h3>Une séquence de PNG</h3>
|
<h3>Une séquence de PNG</h3>
|
||||||
<p class="aide">Toutes les images d'un export Krita « séquence d'images »
|
<p class="aide">Toutes les images d'un export Krita « séquence d'images »
|
||||||
(<code>feu_0000.png</code>, <code>feu_0001.png</code>…), pour une seule animation.</p>
|
(<code>feu_0000.png</code>, <code>feu_0001.png</code>…), pour une seule animation :
|
||||||
|
<strong>{{ max_images }} images au maximum</strong>.</p>
|
||||||
<label class="depot">
|
<label class="depot">
|
||||||
<input type="file" name="images" multiple accept=".png,image/png">
|
<input type="file" name="images" multiple accept=".png,image/png">
|
||||||
<span>Choisir les PNG…</span>
|
<span>Choisir les PNG…</span>
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ Le projet avance en deux temps :
|
|||||||
- Matériel : Pi 3B+ / 4 / Zero 2 W (pas le Pi 5 : `rpi_ws281x` ne le gère pas), GPIO 18 (patte 12) → **74AHCT125** (3,3 V → 5 V, VCC pris sur le fil rouge de la fiche DIN) → 330 Ω → DIN. Audio désactivé (`dtparam=audio=off`).
|
- Matériel : Pi 3B+ / 4 / Zero 2 W (pas le Pi 5 : `rpi_ws281x` ne le gère pas), GPIO 18 (patte 12) → **74AHCT125** (3,3 V → 5 V, VCC pris sur le fil rouge de la fiche DIN) → 330 Ω → DIN. Audio désactivé (`dtparam=audio=off`).
|
||||||
- Installation : `installation/installer.sh` (venv `~/vitrine-env`, services systemd `vitrine-pixels` (lecteur) et `vitrine-web` (interface)). Docs : README, INSTALLATION_PI, GUIDE_KRITA, MODE_VITRINE (overlay FS), INTERFACE_WEB, CODER_EN_PYTHON.
|
- Installation : `installation/installer.sh` (venv `~/vitrine-env`, services systemd `vitrine-pixels` (lecteur) et `vitrine-web` (interface)). Docs : README, INSTALLATION_PI, GUIDE_KRITA, MODE_VITRINE (overlay FS), INTERFACE_WEB, CODER_EN_PYTHON.
|
||||||
- Interface web `serveur_web.py` (Flask, port 80, gabarits dans `web/`) : dépôt GIF/PNG/séquences, ordre (renommage `01_…`), activer/désactiver (`.desactivees/`), corbeille (`.corbeille/`), « jouer maintenant » via post-it `/run/vitrine-pixels/demande` lu par le lecteur.
|
- Interface web `serveur_web.py` (Flask, port 80, gabarits dans `web/`) : dépôt GIF/PNG/séquences, ordre (renommage `01_…`), activer/désactiver (`.desactivees/`), corbeille (`.corbeille/`), « jouer maintenant » via post-it `/run/vitrine-pixels/demande` lu par le lecteur.
|
||||||
- Pi installé : Raspberry Pi 3 Model B, Raspbian 11 Bullseye 32 bits (dossier boot = `/boot`, Pillow 8.1, Flask 1.1), `ssh devel@172.16.7.199` (clé ESADHaR, câble eth0) ; Wi-Fi wlan0 = 172.16.50.27. Le Wi-Fi et le filaire de l'école ne se voient pas : depuis le Wi-Fi, page web sur http://172.16.50.27 (adresses DHCP, affichées en bas de la page). Matrice posée debout : `ROTATION = 90`, image 8 × 32. Pas de 74AHCT125 pendant le workshop : montage A (matrice en 4,4 V) ou B (transistor, `INVERSER_SIGNAL`).
|
- Pi installé : Raspberry Pi 3 Model B, Raspbian 11 Bullseye 32 bits (dossier boot = `/boot`, Pillow 8.1, Flask 1.1), Ethernet débranché : accès uniquement en Wi-Fi, `ssh devel@172.16.50.27` (clé ESADHaR) ; l'ancienne adresse câble était 172.16.7.199. Le Wi-Fi et le filaire de l'école ne se voient pas : depuis le Wi-Fi, page web sur http://172.16.50.27 (adresses DHCP, affichées en bas de la page). Matrice posée debout : `ROTATION = 90`, image 8 × 32. Pas de 74AHCT125 pendant le workshop : **montage A** (GPIO 18 → 330 Ω → DIN, matrice en 4,4 V) testé et validé ; montage B (transistor 2N3904 inverseur) essayé : échec, trop lent (temps de stockage ≈ 0,2 µs face à un « 0 » de 0,4 µs), seule la 1re LED s'allume au hasard. Lecteur `vitrine-pixels` activé au démarrage le 2026-10-08.
|
||||||
- Schémas générés par `outils/schemas.py`, exemples et palette Krita par `outils/generer_exemples.py`.
|
- Schémas générés par `outils/schemas.py`, exemples et palette Krita par `outils/generer_exemples.py`.
|
||||||
|
|
||||||
### Chiffres clés
|
### Chiffres clés
|
||||||
|
|||||||
Reference in New Issue
Block a user