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.
382 lines
16 KiB
Markdown
382 lines
16 KiB
Markdown
<p align="center">
|
||
<img src="BMU.jpeg" alt="PCB BMU" width="480" />
|
||
</p>
|
||
|
||
<h1 align="center">KXKM Batterie Parallelator</h1>
|
||
|
||
<p align="center">
|
||
<strong>Battery Management Unit pour packs 24-30V en parallèle</strong><br>
|
||
Jusqu'à 32 batteries · ESP32-S3 · BLE + MQTT + InfluxDB · App iOS compagnon
|
||
</p>
|
||
|
||
<p align="center">
|
||
<a href="https://github.com/KomplexKapharnaum/KXKM_Batterie_Parallelator/actions/workflows/ci.yml">
|
||
<img src="https://github.com/KomplexKapharnaum/KXKM_Batterie_Parallelator/actions/workflows/ci.yml/badge.svg" alt="CI" />
|
||
</a>
|
||
<a href="https://github.com/KomplexKapharnaum/KXKM_Batterie_Parallelator/actions/workflows/sim-host-tests.yml">
|
||
<img src="https://github.com/KomplexKapharnaum/KXKM_Batterie_Parallelator/actions/workflows/sim-host-tests.yml/badge.svg" alt="QA" />
|
||
</a>
|
||
<img src="https://img.shields.io/badge/ESP--IDF-v5.4-blue" alt="ESP-IDF" />
|
||
<img src="https://img.shields.io/badge/license-GPLv3-green" alt="Licence" />
|
||
<img src="https://img.shields.io/badge/tests-26%20passed-brightgreen" alt="Tests" />
|
||
</p>
|
||
|
||
> 🇬🇧 English version: [README.md](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](https://lelectronrare.fr) pour [KompleX KapharnaüM](https://komplex-kapharnaum.net) (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
|
||
|
||
```bash
|
||
# 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
|
||
|
||
<p align="center">
|
||
<strong>KXKM BMU</strong> — SwiftUI + CoreBluetooth
|
||
</p>
|
||
|
||
| 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)
|
||
|
||
```bash
|
||
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)
|
||
|
||
```bash
|
||
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.
|
||
|
||
<details>
|
||
<summary><strong>Phase 1 — Migration (mars 2026) : 7/7 résolus</strong></summary>
|
||
|
||
| 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 | ✅ |
|
||
|
||
</details>
|
||
|
||
<details>
|
||
<summary><strong>Phase 2 — Audit approfondi (mars-avril 2026) : 13/13 résolus</strong></summary>
|
||
|
||
| 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 |
|
||
|
||
</details>
|
||
|
||
<details>
|
||
<summary><strong>Audit infrastructure (avril 2026) : 5/5 résolus</strong></summary>
|
||
|
||
| 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 |
|
||
|
||
</details>
|
||
|
||
---
|
||
|
||
## 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](https://komplex-kapharnaum.net) 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](https://github.com/KomplexKapharnaum).
|
||
|
||
---
|
||
|
||
## Licence
|
||
|
||
[GNU General Public License v3.0](LICENSE)
|
||
|
||
---
|
||
|
||
<p align="center">
|
||
<sub>Construit avec ESP-IDF, LVGL, NimBLE, SwiftUI, FastAPI, InfluxDB et Grafana.</sub><br>
|
||
<sub>Développé par <a href="https://lelectronrare.fr">L'Electron Rare</a> pour <a href="https://komplex-kapharnaum.net">KompleX KapharnaüM</a></sub>
|
||
</p>
|