Context: the project README is written in English while the client (KompleX KapharnaüM) and field technicians are French-speaking. Approach: full translation of README.md into README.fr.md, keeping tables, badges, code blocks and anchors identical. Changes: - Add README.fr.md (complete French translation) - Cross-link to the English version at the top of the file Impact: French-speaking users get first-class documentation without diverging from the English source of truth.
16 KiB
KXKM Batterie Parallelator
Battery Management Unit pour packs 24-30V en parallèle
Jusqu'à 32 batteries · ESP32-S3 · BLE + MQTT + InfluxDB · App iOS compagnon
🇬🇧 English version: README.md
Vue d'ensemble
Le KXKM Batterie Parallelator est une Battery Management Unit (BMU) embarquée qui met en parallèle, en toute sécurité, plusieurs packs de batteries (24-30V) pour des installations scéniques hors réseau. Chaque batterie est surveillée individuellement (tension, courant, température) et peut être déconnectée en quelques microsecondes via des interrupteurs MOSFET.
Conçu par L'Electron Rare pour KompleX KapharnaüM (Villeurbanne, France) — une compagnie d'arts vivants qui déploie de la scénographie numérique dans l'espace public, sans alimentation secteur.
Fonctionnalités clés
| Fonctionnalité | Description |
|---|---|
| Protection par batterie | Détection sous/sur-tension, surintensité, déséquilibre |
| Reconnexion automatique | Backoff exponentiel avec verrouillage permanent après 5 défauts |
| Équilibrage doux (soft-balancing) | Duty-cycling avec mesure opportuniste de R_int |
| Intégration Victron | Instant Readout BLE + émulation GATT SmartShunt + scanner de périphériques |
| Interface tactile | Écran LVGL 320x240 avec grille batteries, SOH, graphiques |
| App iOS compagnon | Supervision temps réel en BLE, cache hors-ligne, accès par rôles |
| Télémétrie cloud | MQTT + InfluxDB + Grafana avec persistance hors-ligne |
| Mises à jour OTA | Double partition avec rollback automatique |
Démarrage rapide
# Firmware ESP-IDF (production)
cd firmware-idf
source ~/esp/esp-idf/export.sh
idf.py build
idf.py -p /dev/cu.usbmodem* flash monitor
# PlatformIO (Arduino legacy)
pio run -e kxkm-s3-16MB --target upload
# Lancer tous les tests (aucun matériel requis)
pio test -e sim-host # 13 tests PlatformIO
cd firmware-idf && idf.py build # 13 tests host ESP-IDF
# Stack cloud (serveur kxkm-ai)
cd kxkm-api && cp .env.example .env && docker compose up -d
Architecture
┌──────────────────────────┐
│ ESP32-S3-BOX-3 │
│ Écran LVGL 320x240 │
│ NimBLE + WiFi │
└─────────┬────────────────┘
│ I2C 50kHz
┌───────────────┼───────────────┐
│ │ │
┌─────▼─────┐ ┌─────▼─────┐ ┌─────▼─────┐
│ TCA9535 │ │ TCA9535 │ │ ...×8 │
│ 4× MOSFET │ │ 4× LED │ │ │
└─────┬─────┘ └───────────┘ └───────────┘
│
┌─────▼─────┐
│ INA237 ×4 │ ← par TCA (tension + courant)
└───────────┘
┌──────────────────────────────────────────────────────┐
│ Stack Cloud │
│ BMU ──MQTT──► Mosquitto ──► Telegraf ──► InfluxDB │
│ │ │
│ iPhone ◄──BLE──► BMU Grafana ◄───────────┘ │
│ iPhone ◄──REST──► FastAPI ◄─────────────────┘ │
└──────────────────────────────────────────────────────┘
Composants firmware (ESP-IDF, 25 modules)
| Composant | Rôle |
|---|---|
bmu_protection |
Machine à états : 5 états, 5 critères, verrouillage permanent |
bmu_balancer |
Soft-balancing par duty-cycling + mesure R_int |
bmu_ina237 |
16× moniteurs de puissance INA237 (shunt 2mΩ) |
bmu_tca9535 |
8× expanders GPIO (interrupteurs + LEDs) |
bmu_ble |
NimBLE 4 services GATT + Victron SmartShunt |
bmu_ble_victron |
Victron Instant Readout (advertising chiffré AES-CTR) |
bmu_ble_victron_scan |
Scanner BLE central pour périphériques Victron |
bmu_display |
LVGL : grille batteries, infos pack, SOH, graphiques, swipe |
bmu_influx + _store |
Client InfluxDB + persistance hors-ligne FAT/SD |
bmu_mqtt |
ESP-MQTT avec identifiants d'authentification |
bmu_vedirect |
Parseur UART Victron VE.Direct (chargeur solaire) |
bmu_rint |
Mesure de résistance interne par impulsion |
bmu_web |
HTTP + WebSocket avec auth par token + rate limiting |
bmu_config |
Config runtime NVS + clés périphériques Victron |
bmu_storage |
NVS, FAT, SPIFFS, carte SD, USB MSC |
bmu_ota |
Mise à jour firmware OTA avec rollback |
Logique de protection
Nb_switch < 5 → Reconnexion immédiate dès que la condition disparaît
Nb_switch == 5 → Délai de 10 secondes avant reconnexion
Nb_switch > 5 → Verrouillage permanent (LED rouge clignotante) jusqu'au redémarrage
| Seuil | Défaut | Configurable |
|---|---|---|
| Tension min | 24 000 mV | via Kconfig + NVS |
| Tension max | 30 000 mV | via Kconfig + NVS |
| Courant max | 10 000 mA | via Kconfig + NVS |
| Déséquilibre | 1 000 mV | via Kconfig + NVS |
| Délai de reconnexion | 10 000 ms | via Kconfig |
| Limite de commutations | 5 | via Kconfig |
Comportement des LEDs
| État | LED |
|---|---|
| Connectée | Vert fixe |
| Déconnectée | Rouge fixe |
| Erreur | Rouge clignotant (~1 Hz) |
| Verrouillée | Rouge fixe |
App iOS compagnon
KXKM BMU — SwiftUI + CoreBluetooth
| Fonctionnalité | Transport |
|---|---|
| Tableau de bord batteries temps réel | BLE (poll 2s) |
| Détail batterie + graphique de tension | BLE |
| Interrupteur ON/OFF + reset | BLE (selon rôle) |
| Config protection (pas de 0,025 V/A) | BLE |
| Config WiFi de la BMU | BLE |
| Journal d'audit + filtrage | SQLDelight local |
| Mode hors-ligne avec indicateur de cache | Bascule automatique |
| Tableau de bord SOH + R_int | BLE + REST |
| Scan des périphériques Victron | BLE passif |
3 rôles : Admin (complet), Technicien (contrôle), Observateur (lecture seule) — PIN + Face ID.
Code : iosApp/ (Xcode, CoreBluetooth) + kxkm-bmu-app/ (module Kotlin partagé KMP)
Intégration BLE Victron
La BMU interagit avec l'écosystème Victron via trois mécanismes :
| Mécanisme | Direction | Protocole |
|---|---|---|
| Instant Readout | BMU → VictronConnect | Advertising chiffré AES-CTR (PID 0xA389) |
| GATT SmartShunt | BMU → tout client BLE | 9 caractéristiques en lecture seule (V, I, SOC, Ah, TTG, T, alarme) |
| Scanner de périphériques | Périphériques Victron → BMU | Scan BLE passif, déchiffrement AES, cache 8 périphériques |
Types d'enregistrements Victron supportés : Solar (0x01), Battery (0x02), Inverter (0x03), DC-DC (0x04).
Infrastructure cloud
Stack Docker sur le serveur kxkm-ai :
| Service | Port | Rôle |
|---|---|---|
| Mosquitto | 1883, 9001 | Broker MQTT (authentifié) |
| InfluxDB 2.7 | 8086 | Stockage séries temporelles (org=kxkm, bucket=bmu) |
| Telegraf | - | Passerelle MQTT → InfluxDB |
| FastAPI | 8400 | API REST (sync, historique, audit) |
| Grafana 11 | 3001 | 3 tableaux de bord (live, flotte, solaire) |
Installation : cp .env.example .env — renseigner tous les secrets (clé API, token InfluxDB, identifiants MQTT, mot de passe Grafana, origines CORS). Tous les secrets passent par des variables d'environnement ${...}, chmod 600 .env.
Résilience hors-ligne : quand le WiFi/InfluxDB est injoignable, la télémétrie est persistée en flash FAT interne (/fatfs/influx/) avec rotation sur 2 fichiers (512 Ko chacun). Rejeu automatique à la reconnexion.
Tests
26 tests au total — aucun matériel requis.
PlatformIO sim-host (13 tests)
pio test -e sim-host
| Suite | Tests | Couvre |
|---|---|---|
test_protection |
10 | Machine à états V/I/déséquilibre/verrouillage |
test_battery_route_validation |
- | Bornes d'index + vérifications d'état |
test_influx_buffer_codec |
- | Encodage line-protocol InfluxDB |
test_web_mutation_rate_limit |
3 | Fenêtre glissante + multi-IP |
test_web_route_security |
9 | Token en temps constant + Bearer |
test_ws_auth_flow |
7 | Auth combinée + rate limit |
test_mqtt_influx_codec |
3 | JSON MQTT + parsing des topics |
test_emulation_bench |
- | Benchmark d'émulation |
Tests host ESP-IDF (13 tests)
cd firmware-idf && idf.py build
| Suite | Tests | Couvre |
|---|---|---|
test_protection |
13 | Machine à états complète (ESP-IDF) |
test_victron_gatt |
8 | Encodage GATT (V, SOC, alarme, TTG, T) |
test_victron_scan |
5 | Parsing des trames, expiration, MAC, CID |
test_ble_victron |
- | Encodage des trames batterie/solaire |
test_vedirect_parser |
- | Parsing des trames VE.Direct |
test_rint |
- | Calcul de R_int |
test_config_labels |
- | Gestion des étiquettes batterie |
test_vrm_topics |
- | Génération des topics VRM |
Pipelines CI/CD
| Pipeline | Déclencheur | Gate |
|---|---|---|
ci.yml |
push/PR | 13 tests sim-host au vert |
sim-host-tests.yml |
push/PR | + build S3 + RAM ≤75% + Flash ≤85% |
esp-idf-ci.yml |
push/PR | + tests host ESP-IDF + flash ≤85% des 2 Mo OTA |
Occupation flash actuelle : 81 % (1,69 Mo / 2 Mo de partition OTA) — 19 % de marge.
Audit de sécurité
Tous les constats résolus au 2026-04-07.
Phase 1 — Migration (mars 2026) : 7/7 résolus
| ID | Constat | Statut |
|---|---|---|
| CRIT-001 | Appels de fonctions obsolètes | ✅ |
| CRIT-002 | Variable globale Nb_INA non fiable | ✅ |
| CRIT-003 | Accès I2C sans mutex | ✅ |
| CRIT-004 | Initialisation de mutex manquante | ✅ |
| HIGH-005 | Race condition WebSocket V/I | ✅ |
| HIGH-006 | Race condition reconnect_time | ✅ |
| HIGH-007 | Globales non initialisées | ✅ |
Phase 2 — Audit approfondi (mars-avril 2026) : 13/13 résolus
| ID | Sévérité | Constat | Correctif |
|---|---|---|---|
| CRIT-A | Critique | Incohérence d'unités mV/V | ESP-IDF : mV partout |
| CRIT-B | Critique | Déséquilibre comparé à la config au lieu du max de flotte | bmu_protection |
| CRIT-C | Critique | Deadlock dans le switch web | bmu_web |
| CRIT-D | Critique | Routes non authentifiées | bmu_web_security |
| HIGH-1 | Haute | Surintensité négative ignorée | Facteur bidirectionnel |
| HIGH-2 | Haute | WebSocket non authentifié | Token sur la première trame |
| HIGH-3 | Haute | XSS dans /log | Vérification null cJSON |
| HIGH-4 | Haute | MQTT en clair | Auth Mosquitto + .env |
| HIGH-5 | Haute | Changement de vitesse I2C non protégé | I2CLockGuard |
| HIGH-7 | Haute | battery_voltages[] public | state_mutex |
| HIGH-8 | Haute | Crash de la tâche Influx si la SD échoue | Repli influx_store |
| MED-1 | Moyenne | Pas de verrouillage permanent (F08) | BMU_STATE_LOCKED |
| MED-010 | Moyenne | Harmonisation mV/V | mV partout |
Audit infrastructure (avril 2026) : 5/5 résolus
| Constat | Correctif |
|---|---|
| Accès anonyme Mosquitto | Auth + fichier de mots de passe |
| Secrets en dur dans docker-compose | Tout via .env (chmod 600) |
| Token InfluxDB par défaut | Régénéré (44 caractères aléatoires) |
| CORS allow_origins=["*"] | Restreint via variable d'environnement |
| Perte de données hors-ligne | Persistance FAT/SD + rejeu |
Matériel
Révisions du PCB
| Version | Statut | Fichiers |
|---|---|---|
| BMU v1 | Fabriquée | hardware/PCB/ |
| BMU v2 | Prête pour production (ERC 0, BOM 107) | hardware/pcb-bmu-v2/ |
Circuits intégrés clés
| CI | Boîtier | Rôle | Adresse I2C |
|---|---|---|---|
| INA237 | VSSOP-10 | Moniteur de puissance (V/I/P) | 0x40-0x4F (16 max) |
| TCA9535 | TSSOP-24 | Expander GPIO (interrupteurs + LEDs) | 0x20-0x27 (8 max) |
| ISO1540 | SOIC-8 | Isolateur I2C galvanique | - |
| IRF4905 | D2PAK | MOSFET canal P (55V, 74A) | - |
Cartographie I2C
TCA_0 (0x20) → INA 0x40-0x43 → Batteries 1-4
TCA_1 (0x21) → INA 0x44-0x47 → Batteries 5-8
TCA_2 (0x22) → INA 0x48-0x4B → Batteries 9-12
TCA_3 (0x23) → INA 0x4C-0x4F → Batteries 13-16
Contrainte topologique : Nb_TCA × 4 == Nb_INA — toute incohérence déclenche le fail-safe (tout OFF).
Structure du projet
KXKM_Batterie_Parallelator/
├── firmware-idf/ # Firmware ESP-IDF 5.4 (25 composants)
│ ├── components/ # Composants ESP-IDF modulaires
│ ├── main/ # Point d'entrée app_main
│ └── test/ # Tests host Unity (13 suites)
├── firmware/ # Firmware PlatformIO/Arduino legacy
│ ├── src/ # Modules source
│ ├── test/ # Tests sim-host (8 suites, 13 tests)
│ └── lib/INA237/ # Fork local du driver INA237
├── iosApp/ # App iOS SwiftUI (CoreBluetooth)
├── kxkm-bmu-app/ # Module Kotlin partagé KMP
│ └── shared/ # Logique métier multiplateforme
├── kxkm-api/ # Stack Docker (Mosquitto, InfluxDB, FastAPI, Grafana)
├── hardware/ # Schémas KiCad + Gerbers (v1 + v2)
├── specs/ # Gates Kill_LIFE (S0-S3)
├── docs/ # Specs, plans, gouvernance, recherche
├── grafana/dashboards/ # 3 dashboards Grafana JSON
└── scripts/ # CI, QA, pipeline ML
À propos de KompleX KapharnaüM
KompleX KapharnaüM est une compagnie d'arts vivants basée à Villeurbanne (Lyon, France), active depuis plus de 20 ans dans les arts de rue, le spectacle vivant et la création numérique. La compagnie développe sa propre plateforme matérielle embarquée (K32, à base d'ESP32) et maintient des outils open source sur GitHub.
Licence
GNU General Public License v3.0
Construit avec ESP-IDF, LVGL, NimBLE, SwiftUI, FastAPI, InfluxDB et Grafana.
Développé par L'Electron Rare pour KompleX KapharnaüM
