diff --git a/debian-qt5/changelog b/debian-qt5/changelog index 65560a5..120b22f 100644 --- a/debian-qt5/changelog +++ b/debian-qt5/changelog @@ -1,5 +1,12 @@ powersync-energy-plugin-nymea (1.15.2+etm43) trixie; urgency=medium + * DÉPLOYÉ SUR .75 le 2026-09-01, version relue sur la box et `nymead` actif. Les quatre motifs + du §10 sont présents dans le binaire posé — vérifié sur machine, pas déduit du script. + * Piège de vérification, noté : `authorisedW`, `perPhaseBound` et `binding` sont des + QStringLiteral, donc stockés en UTF-16 et INVISIBLES à `strings` par défaut. Un premier + relevé les donnait absents. `strings -el` les trouve. Vérifier une chaîne dans un binaire + demande de savoir comment elle y est encodée. + * ECO_FLOOR_MISSED — le motif a désormais son ÉMETTEUR, et c'est pourquoi il est créé maintenant. Ce matin il n'en avait pas : le créer aurait posé un invariant sur un code que rien ne produit, un test toujours vert. La bascule de période (LM-1013-c) l'a fait naître — diff --git a/docs/DESIGN_COMMANDE_PERDUE.md b/docs/DESIGN_COMMANDE_PERDUE.md new file mode 100644 index 0000000..3914218 --- /dev/null +++ b/docs/DESIGN_COMMANDE_PERDUE.md @@ -0,0 +1,107 @@ +# DESIGN — la commande perdue, et l'écart que personne ne mesure + +*(Ouvert le 2026-09-01, sur le premier constat de `RELEVE_APP_20260831.md`. Le design avant le +code, comme pour le délestage et le §10.)* + +## 1. Le fait, tel qu'il a été mesuré + +Coupure Modbus. La box journalise `Restoring manual values of false 6 A` trois secondes après. +La borne, elle, reste à `power: true`, **7 A** — la consigne que l'arbitre lui avait donnée au +cycle d'avant. Le lien revient : **rien ne réapplique**. En mode manuel personne ne commande, +donc l'écart est **permanent**. + +**4,4 kW réellement tirés, `draw.committedW` à 0, un budget de surplus de 2 349 W qui ignore ces +watts.** + +## 2. Ce que le fait dit vraiment — et il est plus large que son titre + +« Réappliquer la commande de l'arbitre » ne décrit pas le cas : **en mode manuel, l'arbitre ne +commande pas**. Ce qui s'est passé est autre chose, et c'est pire : + +1. Le proxy amont a voulu restaurer des valeurs manuelles (`false 6 A`) ; +2. l'écriture a échoué pendant l'indisponibilité ; +3. **personne ne l'a rejouée** ; +4. l'organe est resté sur une consigne **dont plus aucun commandeur ne se réclame** — ni + l'arbitre, qui ne commande plus, ni le mode manuel, dont la consigne n'a pas atterri. + +> **La troisième forme de la même famille.** Après la télémétrie qui ne distinguait pas une +> issue de son contraire (`ECS-111`) et la valeur de retour qui faisait passer un refus pour un +> succès (`ECS-415`), voici **l'exécution** : une écriture perdue, un état sain en apparence, +> `available` à vrai, et personne pour savoir que la commande n'a jamais atterri. + +## 3. Ce que le moteur a déjà — et ce qui manque + +| Pièce | État | +|---|---| +| Dernière consigne écrite, charges en watts | **existe** — `m_currentSetpointW`, `m_currentStage` | +| Dernière consigne écrite, **bornes VE** | **MANQUE** — `EvAdapter` ne garde que `m_lastActionAt` | +| Ce que l'organe FAIT | **existe** — `EvCharger::maxChargingCurrent()`, états du Thing | +| Ce que l'organe MESURE, et ce que ça vaut | **existe** — `deviceMeasurement()`, `measurement.source` (`LM-1105`/`LM-1106`) | +| Détection de la FIN d'une indisponibilité | **MANQUE** — le seul chemin est `clearFault()`, actionné par l'opérateur | +| Comparaison commandé / constaté | **MANQUE** — nulle part | + +**Le constat porte sur les bornes, et c'est précisément là que la mémoire de la commande manque.** +Ce n'est pas un hasard : les adaptateurs de charges en watts gardent leur consigne parce qu'ils +en ont besoin pour l'écrêtage de verrou ; la borne, elle, était pilotée par le proxy amont, qui +n'a jamais eu à se relire. + +## 4. LES DÉCISIONS + +**4.1 — Réappliquer, ou signaler ?** +Réappliquer automatiquement une consigne vieille de plusieurs minutes est un geste **dangereux** : +le monde a changé pendant l'indisponibilité — le surplus, l'heure, l'échéance. Rejouer une +décision périmée, c'est décider avec des données mortes, exactement ce que le mode dégradé L2 +refuse de faire. + +**Recommandation : SIGNALER toujours, RÉAPPLIQUER jamais telle quelle.** Au retour de +disponibilité, le cycle normal reprend la main et décide sur des données fraîches — c'est déjà +le bon comportement. Ce qui manque n'est pas le rejeu, c'est que **l'écart soit dit tant qu'il +dure**. Et il dure indéfiniment quand personne ne reprend la main, ce qui est le cas du relevé. + +**4.2 — Que publie-t-on, et sous quelle forme ?** +Un écart n'a de sens que rapporté à ce qu'on sait de l'organe. Proposition : un objet `command` +au niveau de la charge, publié **seulement quand la comparaison est possible** — + +```jsonc +"command": { "expectedW": 1380, "observedW": 4400, "divergent": true, "since": "…" } +``` + +**`measurement.source` conditionne ce que vaut cette comparaison**, et doit donc la gouverner : +contre `none`, il n'y a rien à comparer et l'objet est **absent** — pas `divergent: false`, qui +affirmerait la concordance qu'on ne peut pas constater. Même règle que partout ailleurs ici. + +**4.3 — Et la COMPTABILITÉ, qui est le vrai coût.** +`draw.committedW` à 0 pendant que 4,4 kW sont tirés n'est pas un défaut d'affichage : le budget +de surplus est calculé sans ces watts, donc **le moteur alloue de l'énergie qui n'existe pas**. +Une charge que l'arbitre ne commande plus mais qui consomme est de la **consommation fixe**, au +même titre que la base de la maison — c'est exactement le raisonnement d'`ECS-410` pour une +charge gelée, appliqué à une cause nouvelle. + +**Recommandation : la retrancher du budget, sans l'allouer.** Elle n'est pas servie, elle est +subie. La compter comme allouée serait prétendre l'avoir décidée. + +> **Sur 18 kVA c'est invisible ; sur 6 kVA c'est le disjoncteur.** Et le délestage ne rattrape +> pas ce cas : il agit sur la marge MESURÉE, qui inclut bien ces 4,4 kW — donc il protège. Ce +> qui reste faux est le **budget**, qui promet un surplus déjà consommé. + +**4.4 — Faut-il un état « commande en vol perdue » distinct d'un défaut ?** +`ECS-410`/`ECS-414` couvrent l'écriture qui échoue trois fois → défaut. Ici l'écriture a échoué +**pendant une indisponibilité**, et la charge est ensuite redevenue disponible : le défaut se +lève, la divergence reste. À trancher : est-ce un `faultCode` de plus, ou un état orthogonal ? + +## 5. ORDRE DE TRAVAIL — deux étapes à comportement constant d'abord + +**Étape 1 — la MÉMOIRE de la commande, sans rien en faire.** `EvAdapter` retient sa dernière +consigne appliquée, comme les autres adaptateurs le font déjà. Publication seule, comportement +constant. Sans cette pièce, aucune des suivantes n'est calculable. + +**Étape 2 — l'ÉCART publié, sans décider dessus.** L'objet `command`, gouverné par +`measurement.source`. C'est la moitié qui rend le défaut **visible**, et elle ne change aucune +décision — même stratégie qu'à l'étape C du §10, où publier le régime a précédé de décider avec. + +**Étape 3 — la COMPTABILITÉ corrigée.** La conso subie sort du budget. C'est ici que le +comportement change, et c'est la seule étape qui le fasse. + +**Étape 4 — la reprise en main**, si elle est jugée nécessaire après avoir vu les traces des +étapes 2 et 3. **Délibérément en dernier** : c'est la seule qui commande du matériel, et on +saura alors combien de fois le cas se produit réellement.