`computeEcoTarget()` convertit une obligation en énergie en une cible en watts : cadence = reste / temps restant, recalculée à chaque cycle. Une cadence figée au départ ne rattrape pas — après une heure sans surplus elle reste à la cadence initiale et manque l'échéance en ayant l'air de travailler. Mesurer d'abord était nécessaire, pas rituel : aucun scénario existant ne portait d'obligation éco, donc aucune référence. Les valeurs ont été relevées par sonde avant toute assertion — et c'est le relevé qui a trouvé un défaut que la lecture du code ne montrait pas. LA BASCULE DE PÉRIODE (LM-1013-c). À l'instant de l'échéance quotidienne, resolveDeadline() vise la prochaine occurrence : une nouvelle obligation s'ouvre. Sans reprise de la base d'avancement, le lendemain hérite de l'énergie de la veille et se croit tenu sans avoir rien reçu. La sonde affichait 83 W et 86400 s restantes à 07:00 pile, ce qui n'avait aucun sens tant qu'on ne cherchait pas la période. Contre-épreuve : 1800 Wh portés au crédit du jour suivant. Limite assumée et écrite : l'obligation précédente, si elle n'était pas tenue, est abandonnée SANS motif dédié. Le dire demande un ECO_FLOOR_MISSED que rien n'émet ; le journal le dit, l'écran non. Le durcissement est relevé point par point — 333 W à six heures, 2 kW à une heure, le plafond dès seize minutes — parce que c'est la courbe que le motif devra rendre lisible, pas une valeur isolée. Point de non-retour nommé plutôt que subi : un seuil se discute, une asymptote non. R6 armée dans le calcul : sous `unmeasurable`, jamais « tenue ». On vise l'obligation entière, le choix qui sert plutôt que celui qui suppose acquis. Suite : loadmodel 22/22, simulation 56/56, charging 48/48 identique à la référence ligne à ligne, spotmarket 32/32, doxygen 0 avertissement. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015F7G5VeaPVSMeVNjiGj36p
1671 lines
117 KiB
Plaintext
1671 lines
117 KiB
Plaintext
powersync-energy-plugin-nymea (1.15.2+etm41) trixie; urgency=medium
|
||
|
||
* §10 étape D (1/2) — L'ÉTALEMENT UNIFORME, mesuré avant d'être asserté. `computeEcoTarget()`
|
||
convertit une obligation en ÉNERGIE en une cible en WATTS : cadence = reste / temps restant,
|
||
recalculée à chaque cycle. « Au plus tard » serait optimal mais suppose une prévision, qui
|
||
n'existe pas dans ce modèle (LM-1013-b).
|
||
* MESURÉ D'ABORD, ET C'ÉTAIT NÉCESSAIRE. Aucun scénario existant ne portait d'obligation éco —
|
||
`LoadContext::needs` n'était rempli par aucun adaptateur — donc aucune référence. Les valeurs
|
||
ont été relevées par sonde avant toute assertion, et c'est le relevé qui a révélé un défaut
|
||
que la lecture du code ne montrait pas (voir ci-dessous).
|
||
* LE DURCISSEMENT, relevé point par point : 2 kWh restants font 333 W à six heures de
|
||
l'échéance, 2 kW à une heure, et le plafond dès SEIZE minutes. La fenêtre où l'achat devient
|
||
massif n'arrive pas progressivement, elle arrive d'un coup — c'est pourquoi le motif devra
|
||
publier la cadence ET le temps restant.
|
||
* POINT DE NON-RETOUR nommé : sous un seuil, on cesse d'étaler et on prend le maximum. Un seuil
|
||
se discute, une asymptote se subit.
|
||
* LM-1013-c — LA BASCULE DE PÉRIODE, trouvée en mesurant. À l'instant de l'échéance
|
||
quotidienne, `resolveDeadline()` vise la prochaine occurrence : une nouvelle obligation
|
||
s'ouvre. Sans reprise de la base d'avancement, LE LENDEMAIN HÉRITE DE L'ÉNERGIE DE LA VEILLE
|
||
et se croit tenu sans avoir rien reçu. Contre-épreuve : 1800 Wh portés au crédit du jour
|
||
suivant. Limite assumée : l'obligation non tenue est abandonnée sans motif dédié — le journal
|
||
le dit, l'écran non, faute d'un ECO_FLOOR_MISSED que rien n'émet.
|
||
* R6 armée dans le calcul : sous un régime `unmeasurable`, on ne conclut JAMAIS « tenue ». On
|
||
vise l'obligation entière — le choix qui sert plutôt que celui qui suppose acquis.
|
||
* L'heure quotidienne vise la PROCHAINE occurrence : lue à 23:00, une limite de 07:00 vise le
|
||
lendemain. La lire comme dépassée ferait franchir le point de non-retour toutes les nuits.
|
||
* Suite : loadmodel 22/22, simulation 56/56, charging 48/48 identique à la référence,
|
||
spotmarket 32/32, doxygen 0 avertissement.
|
||
|
||
-- Patrick Schurig <etm.schurig@gmail.com> Mon, 31 Aug 2026 09:00:00 +0200
|
||
|
||
powersync-energy-plugin-nymea (1.15.2+etm40) trixie; urgency=medium
|
||
|
||
* §10 étape C — L'AVANCEMENT BRANCHÉ EN LECTURE SEULE, et le RÉGIME publié (R5).
|
||
`progress.regime` ∈ measured | estimated | unmeasurable, porté par le NIVEAU. Sous
|
||
`unmeasurable`, `deliveredWh` est ABSENT et jamais 0 : une dégradation ne doit pas se lire
|
||
comme un avancement nul (LM-1009 C1).
|
||
* LE PIÈGE, mesuré et évité (LM-1012-b). `sessionEnergy` est l'ACCUMULATEUR du plugin V2C,
|
||
pas un compteur remis à zéro pour nous : l'écart avec `chargeEnergy` est constant au
|
||
dix-millième (7,2044 kWh sur deux échantillons à deux heures d'écart). Le lire directement
|
||
ferait croire toute obligation tenue de 7,2 kWh dès le premier cycle. L'avancement est donc
|
||
une DIFFÉRENCE depuis une base — contre-épreuve : lecture directe, le test annonce 7204.
|
||
* Base prise à l'ouverture de l'OBLIGATION, jamais au démarrage de nymead : sinon une recharge
|
||
de nuit relue au matin repart de zéro. RECAPTURÉE sur un recul franc de l'accumulateur
|
||
(nouvelle session), pas sur un recul infime, qui est du bruit de lecture.
|
||
* CE QUE LE MOTEUR NE REFAIT PAS : la détection de remise à zéro de la BORNE vit dans le
|
||
plugin, qui interroge à 30 s ; le moteur travaille à la minute et raterait des cycles
|
||
entiers. Regarder comment le plugin l'avait écrite a évité une seconde accumulation, moins
|
||
fine, en concurrence de la première.
|
||
* Trois maillons manquants raccordés au passage, tous déclarés et alimentés par personne :
|
||
`EvCharger` n'exposait pas `sessionEnergy` ; `LoadContextTelemetry::sessionWh` n'était rempli
|
||
nulle part ; et `toLoadContext()` ABANDONNAIT les besoins déclarés, si bien que
|
||
`LoadContext::needs` n'était rempli par aucun adaptateur — l'obligation éco n'existait qu'en
|
||
configuration.
|
||
* Mock : `sessionEnergy` ajouté à `chargerPhaseSwitching` UNIQUEMENT. `charger` reste sans, ce
|
||
qui donne le cas `unmeasurable` nativement — la suite porte les deux régimes, comme le banc.
|
||
* Le régime est publié ALORS QU'AUCUNE DÉCISION NE S'Y APPUIE, délibérément : cela sépare
|
||
publier le régime de décider avec, comme `measurement` l'a été de l'arbitrage, et rend R6
|
||
testable le jour où le motif naîtra plutôt qu'un invariant sur du code que rien n'émet.
|
||
* Rapport amont rédigé pour le SIGSEGV de `Logger::log()` (docs/upstream/) : « hors de ce
|
||
lot » ne veut pas dire sans conséquence — un plantage du moteur de journalisation, c'est
|
||
nymead qui tombe chez un client et le HEMS qui cesse de piloter.
|
||
* Suite : simulation 56/56, charging 48/48 identique à la référence, loadmodel 21/21,
|
||
spotmarket 32/32, doxygen 0 avertissement.
|
||
|
||
-- Patrick Schurig <etm.schurig@gmail.com> Mon, 31 Aug 2026 08:15:00 +0200
|
||
|
||
powersync-energy-plugin-nymea (1.15.2+etm39) trixie; urgency=medium
|
||
|
||
* §10 étape B — `draw` publie l'AUTORISATION, et elle se lit au lieu d'être forgée.
|
||
`authorisedW`, `remainingW`, `binding` et `perPhaseBound` étaient absents parce qu'aucun
|
||
plafond de soutirage n'existait ; le lot délestage en a créé un. Le refus d'origine visait
|
||
la FORGE, pas la lecture — et son motif est tombé sans que sa formulation ne bouge.
|
||
* `committedW` publié TOUJOURS, les quatre autres SEULEMENT si un plafond est en vigueur.
|
||
Le zéro est une valeur quand la grandeur existe, l'absence en est une quand elle n'existe
|
||
pas — un `authorisedW: 0` avec un `binding` par défaut afficherait un plafond inexistant
|
||
avec sa source.
|
||
* `authorisedW` est SIGNÉ : négatif, la maison est déjà au-delà du plafond, et l'ampleur du
|
||
dépassement se lit. L'écrêter confondrait « pile à la limite » et « on dépasse de 1200 W ».
|
||
L'identité `authorisedW − committedW == remainingW` tient toujours ; `remainingW` peut être
|
||
négatif, et c'est un FAIT — les bornes VE engagent du soutirage borné par LEUR phase.
|
||
* ÉCRIT EN TOUTES LETTRES DANS LE CONTRAT, pas en note : sous la réserve batterie,
|
||
`budget.remainingW == 0` et `draw.remainingW != 0` dans la MÊME trame. La réserve annule le
|
||
surplus, jamais l'autorisation de soutirage — celle-ci est une contrainte physique du
|
||
branchement (LM-1203-b vue depuis la frontière). Un client aurait raison de signaler
|
||
l'incohérence si personne ne la lui avait dite.
|
||
* LE TEST D'ABSENCE EST MORT PROPREMENT, et son successeur dit à quelle condition il devient
|
||
faux — application directe du troisième mode d'érosion. L'ancien épinglait « pas de
|
||
authorisedW » et a survécu à son motif ; le nouveau épingle la RÈGLE (présent si le plafond
|
||
existe, absent sinon) et nomme les deux évolutions qui le périmeraient.
|
||
* Suite : simulation 55/55, loadmodel 21/21, spotmarket 32/32, charging 48/48 identique à la
|
||
référence ligne à ligne, doxygen 0 avertissement. Contre-épreuve : réserve annulant aussi
|
||
l'autorisation → 0 au lieu de 3450.
|
||
* NON EXPLIQUÉ, ET SIGNALÉ COMME TEL : un plantage unique de `charging` (SIGSEGV) pendant un
|
||
lot de fond, non reproductible en trois séquences. Le vidage le situe dans
|
||
`Logger::log()` de libnymea.so.1, atteint par un événement posté depuis libnymea-core —
|
||
AUCUNE trame ETM dans la pile. Hors de ce lot, mais consigné plutôt que relancé jusqu'à
|
||
disparaître.
|
||
|
||
-- Patrick Schurig <etm.schurig@gmail.com> Mon, 31 Aug 2026 07:20:00 +0200
|
||
|
||
powersync-energy-plugin-nymea (1.15.2+etm38) trixie; urgency=medium
|
||
|
||
* §10 étapes A1 et A2 — décider et émettre se séparent, à comportement CONSTANT. La chaîne de
|
||
sélection du motif devient une fonction pure (`choisirMotif`), puis `decideSetpoint()` rend
|
||
une CIBLE dont `emitSetpointAction()` forme la commande. Tant que les deux étaient le même
|
||
geste, une seconde passe était littéralement inexprimable.
|
||
* La contrainte du lot est tenue et vérifiée sur le fichier produit : vouluW capturé AVANT
|
||
l'écrêtage de verrou (ECS-309), écrêtage AVANT le décrément du budget (ECS-306), les deux
|
||
dans la phase de décision. Un déplacement d'un cran publierait des motifs faux sans qu'aucun
|
||
nombre ne bouge. Remaniement fait par ALIAS DE RÉFÉRENCE, pour que les vingt lignes
|
||
d'affectation ne soient pas retouchées : ce qui n'est pas réécrit ne peut pas dériver.
|
||
* [reproductibilité] UN VERDICT DE TEST NE DOIT PAS DÉPENDRE DE L'HEURE. Six appels à
|
||
`update(QDateTime::currentDateTime())` manquaient la garde ENERGY_SIMULATION que leurs
|
||
voisins ont : setPhasePowerLimit, setAcquisitionTolerance, et trois dans onActionExecuted.
|
||
Un cycle d'arbitrage complet partait donc à l'horloge MURALE au milieu d'une suite qui
|
||
injecte un temps simulé ; l'armement paresseux (ECS-412) figeait m_lastSwitch sur l'heure
|
||
qu'il était, et un scénario daté en dur se déroulait « avant » son propre armement.
|
||
* PREUVE DU CORRECTIF : la garde rendue, testL4ShedsAPacAndSaysItForcedTheLock repasse avec
|
||
une DATE FIXE, trois fois sur trois — ce qu'il ne pouvait pas faire. L'ancrage sur l'horloge
|
||
réelle était un contournement et il est retiré.
|
||
* `tools/check-simulation-clock.py`, câblé en 4/4 de ci-quality.sh, et VÉRIFIÉ MORDANT.
|
||
Pourquoi une vérification statique et pas un test : la suite ne peut pas dire d'elle-même
|
||
que son verdict dépend de l'heure, puisque c'est le verdict qui ment. Le défaut n'a été
|
||
trouvé que parce qu'il échouait CINQ FOIS SUR CINQ — une fenêtre plus étroite ne basculerait
|
||
qu'une fois sur vingt, passerait pour une fragilité de temporisation, et serait relancée
|
||
jusqu'à ce qu'elle passe. La vérification a d'ailleurs trouvé trois des six déclencheurs
|
||
dès son premier passage.
|
||
* Suite complète : simulation 54/54, charging 48/48 identique à la référence ligne à ligne,
|
||
loadmodel 21/21, spotmarket 32/32, doxygen 0 avertissement.
|
||
|
||
-- Patrick Schurig <etm.schurig@gmail.com> Mon, 31 Aug 2026 01:10:00 +0200
|
||
|
||
powersync-energy-plugin-nymea (1.15.2+etm37) trixie; urgency=medium
|
||
|
||
* Délestage §4 — LA PROTECTION DE SURCHARGE NE SE LIMITE PLUS AUX BORNES. Une PAC ou un
|
||
routeur pouvaient tenir un dépassement sans que rien ne les redescende ; L4 déleste
|
||
désormais les charges du waterfall, de la DERNIÈRE SERVIE à la première — le même ordre
|
||
total que la cascade, lu à l'envers (LM-1211).
|
||
* DÉLESTER N'EST PAS TOUJOURS RETIRER DES WATTS. Une PAC ne se module pas : on la fait
|
||
redescendre d'ÉTAT, vers 2 et jamais 1 (ECS-413). C'est exactement le cas que le bornage de
|
||
la cascade ne pouvait pas traiter — il empêche d'allouer, il ne retire rien à personne.
|
||
* LE FRANCHISSEMENT DE VERROU SE CONSTATE, IL NE SE PRÉSUME PAS (LM-1211-b). Le délestage
|
||
tente d'abord SANS forcer et ne marque `minOnBypassed` que si le verrou a réellement refusé.
|
||
Le déduire d'un verrou simplement ACTIF serait faux : un verrou peut être armé et permettre
|
||
malgré tout le geste demandé. Le marqueur annoncerait un court-cycle qui n'a pas eu lieu, et
|
||
le premier compresseur réellement maltraité passerait inaperçu parmi les fausses alertes.
|
||
* Ce constat n'était pas possible avant ECS-415 : tant qu'un refus renvoyait l'action
|
||
DEMANDÉE, il se lisait comme un succès. Le correctif du contrat était la précondition.
|
||
* ON NE FORCE QUE POUR UN PLAFOND SUBI (LM-1211-c). Breaker et GridOperator autorisent le
|
||
franchissement, EcoSelfImposed jamais — on renonce au plancher éco plutôt qu'au matériel.
|
||
Comportement OBSERVÉ et non postulé : forçage interdit, la PAC de test reste en état 4.
|
||
* UN SEUL COMMANDEUR PAR ORGANE (LM-1211-a). Les charges délestées sont revendiquées pour le
|
||
cycle et le dispatch les saute ; sans cela la consigne du plan rallumerait ce que la
|
||
sécurité vient de couper. Même mécanisme que m_waterfallEvIds pour les bornes.
|
||
* FENÊTRE D'EXÉCUTION ÉTROITE, et les deux autres placements sont consignés dans
|
||
DESIGN_DELESTAGE §6bis parce qu'ils se représenteront : APRÈS le dispatch on lit un état
|
||
périmé (aucune boucle d'événements dans un même update()), et AVANT syncAdapters()
|
||
l'écriture part avec l'adaptateur reconstruit (ECS-412). La bonne fenêtre est entre les deux.
|
||
* Non couvert, et le rester tant qu'aucun émetteur n'existe : GridOperator et EcoSelfImposed
|
||
n'ont pas de source. La branche qui les distingue est écrite et son effet est démontré par
|
||
contre-épreuve, mais aucun test de bout en bout ne peut les exercer — en écrire un poserait
|
||
un invariant sur un code que rien n'émet. Ils viendront avec le §14a.
|
||
* Suite complète : simulation 54/54, charging 48/48 IDENTIQUE à la référence ligne à ligne
|
||
(les bornes restent au chemin amont), loadmodel 21/21, spotmarket 32/32, doxygen 0
|
||
avertissement. Deux contre-épreuves distinctes : délestage débranché, la PAC reste à 4 ;
|
||
forçage interdit, elle reste à 4 aussi — et c'est le comportement voulu d'un plafond
|
||
auto-imposé.
|
||
|
||
-- Patrick Schurig <etm.schurig@gmail.com> Mon, 31 Aug 2026 00:20:00 +0200
|
||
|
||
powersync-energy-plugin-nymea (1.15.2+etm36) trixie; urgency=medium
|
||
|
||
* ECS-415 — UN REFUS DOIT SE DISTINGUER D'UNE APPLICATION, par la valeur de retour.
|
||
`SgReadyAdapter::applyAction()` renvoyait, quand le verrou minStateHold refusait la
|
||
transition, l'action DEMANDÉE telle quelle : un appelant qui relisait `state` y retrouvait
|
||
l'état qu'il venait de demander et concluait au succès, alors que rien n'avait été écrit et
|
||
que les contacts n'avaient pas bougé.
|
||
* CE N'EST PAS UN DÉFAUT DU DÉLESTAGE, C'EST UN DÉFAUT DE CONTRAT. N'importe quelle descente
|
||
d'état pouvait échouer en silence, et `available` restait vrai — la charge avait l'air saine
|
||
pendant que la PAC tirait 3 kW. Même famille qu'ECS-111 : une issue indiscernable de son
|
||
contraire, transposée cette fois au RETOUR d'une commande au lieu de la télémétrie.
|
||
* Toute sortie de `applyAction()` décrit désormais l'état RÉELLEMENT TENU — `state` et
|
||
`estimatedPowerW`, ou `powerW` et `estimatedPowerW` pour les mécanismes en watts. Six
|
||
sorties ramenées à une forme unique côté SG-Ready ; l'idempotence comprise, parce qu'un
|
||
appelant qui demande « état 4, 0 W » sur une PAC qui en tire 3000 doit se voir rendre 3000
|
||
et non son propre zéro. Même correction sur la branche « motif vide » du RelayRouter et de
|
||
l'EtmVariableLoadAdapter ; leurs branches de défaut étaient déjà justes.
|
||
* DÉFAUT LATENT, DEVENU GRAVE À L'USAGE. Aucun appelant ne lisait ce retour — le dispatch
|
||
l'ignore — donc rien ne le révélait. Le premier à s'y fier fut le délestage L4 en cours
|
||
d'écriture, qui a publié 3 kW rendus par une PAC restée en état 4. Un contrat qu'aucun
|
||
appelant n'exerce n'est pas un contrat tenu : c'est un contrat non testé.
|
||
* `m_currentState` n'était PAS en cause : il est écrit synchroniquement, sans attendre aucun
|
||
acquittement, et le repli sur échec d'écriture passe par settleTransition(). L'hypothèse
|
||
d'une écriture conditionnée à un retour tardif était fausse, et c'est la sonde qui l'a dit.
|
||
* ECS-411-b ne s'applique pas ici : la relecture des contacts (construction, ClearLoadFault)
|
||
répond à une divergence entre état interne et matériel. Il n'y en avait aucune — l'état
|
||
interne était juste, c'est le RETOUR qui mentait.
|
||
* Trois pièges de l'étape 4 consignés dans DESIGN_DELESTAGE §6bis, y compris les deux
|
||
placements écartés : après le dispatch on lit un état périmé (aucune boucle d'événements
|
||
dans un même update()), et avant syncAdapters() l'écriture part avec l'adaptateur reconstruit
|
||
(ECS-412). L'étape 4 elle-même reste NON LIVRÉE.
|
||
* Suite complète : simulation 53/53, charging 48/48, loadmodel 21/21, spotmarket 32/32,
|
||
doxygen 0 avertissement. Contre-épreuve : sur l'ancien retour, `refuse.state` vaut 2 —
|
||
l'état demandé — au lieu de 4.
|
||
|
||
-- Patrick Schurig <etm.schurig@gmail.com> Sun, 30 Aug 2026 23:30:00 +0200
|
||
|
||
powersync-energy-plugin-nymea (1.15.2+etm35) trixie; urgency=medium
|
||
|
||
* Délestage §3 — LA CASCADE TIENT DANS DEUX RESSOURCES. Le surplus dit ce qu'on peut dépenser
|
||
sans acheter ; l'autorisation de soutirage dit ce que le branchement supporte. Une charge
|
||
doit tenir dans les deux. Jusqu'ici seules les bornes VE étaient bornées par la seconde : une
|
||
charge de waterfall pouvait créer un dépassement que seule la protection L4 rattrapait, après
|
||
coup. C'est le changement de comportement du lot, et il ne touche QUE les charges non-EV.
|
||
* Motif `DRAW_CAP` (R8) — porte `marginW` (SIGNÉ), `overshootW`, `source` (code opaque :
|
||
connection | gridOperator | selfImposed) et `phase` (A/B/C, ABSENT si le plafond est un
|
||
total — son absence dit qu'aucune phase n'est coupable, elle ne vaut pas « phase A »).
|
||
Chacune commande un geste différent, et aucune ne se déduit des autres.
|
||
* PRÉCÉDENCE LM-1011, déjà gravée, appliquée telle quelle : `DRAW_CAP` uniquement quand
|
||
l'autorisation résiduelle est NULLE. S'il en reste, fût-ce moins que le plus petit palier,
|
||
c'est `BELOW_MIN_POWER` — « il y en a, mais pas au bon format ». Le soutirage hérite du
|
||
couple que le surplus avait déjà.
|
||
* Les bornes VE ne sont PAS écrêtées par la marge de la maison, et ce n'est pas un oubli :
|
||
leur plafond de cycle connaît LES PHASES QU'ELLES OCCUPENT. Leur appliquer en plus la pire
|
||
phase les priverait d'une phase saine parce qu'une autre sature. Entre deux bornes justes on
|
||
garde la mieux informée, pas la plus sévère. Elles décrémentent en revanche la marge, comme
|
||
toutes les autres : ne décompter que les charges qu'on borne offrirait aux suivantes une
|
||
marge déjà dépensée.
|
||
* LIMITE DE MODÈLE, ASSUMÉE ET DOCUMENTÉE (LM-1210-a) : `LoadDeclared::phases` est un NOMBRE,
|
||
pas un masque — le moteur ignore sur quelle phase une charge est câblée. La marge retenue est
|
||
donc celle de la PIRE phase. Le pessimisme est borné en pratique (une phase qui exporte a une
|
||
marge plus grande que le plafond et ne borne personne) et coûte du rendement, jamais de la
|
||
sécurité. Lever la limite demande un masque déclaré : config, interface, migration — hors lot.
|
||
* Suite complète : simulation 52/52, charging 48/48 IDENTIQUE à la référence ligne à ligne
|
||
(les bornes ne bougent pas, c'est l'invariant du lot), loadmodel 21/21, spotmarket 32/32,
|
||
doxygen 0 avertissement. Contre-épreuve : bornage débranché, la charge est servie 1000 W
|
||
alors que l'autorisation est dépassée de 460 W.
|
||
|
||
-- Patrick Schurig <etm.schurig@gmail.com> Sun, 30 Aug 2026 21:15:00 +0200
|
||
|
||
powersync-energy-plugin-nymea (1.15.2+etm34) trixie; urgency=medium
|
||
|
||
* [upstream-fix] rootmeter — `calculateAllowanceAmpere()` lisait un AUTRE état que celui
|
||
qu'elle testait. Les branches des phases B et C testaient `currentPhaseB`/`currentPhaseC`
|
||
(ampères) puis lisaient `currentPowerPhaseB`/`currentPowerPhaseC` divisés par 230 (watts),
|
||
là où la phase A lit bien l'état qu'elle teste.
|
||
* CE CORRECTIF CHANGE LE COMPORTEMENT, ET DANS LE BON SENS. Des bornes seront écrêtées qui ne
|
||
l'étaient pas : ce n'est pas une régression, c'est la protection de surcharge qui REDEVIENT
|
||
EFFECTIVE sur B et C. Un compteur publiant les courants par phase sans les puissances par
|
||
phase entrait dans la première branche, y lisait 0, et se voyait accorder une place libre
|
||
égale au plafond ENTIER — la protection n'écrêtait rien et ne signalait rien. Sur une
|
||
triphasée déséquilibrée, le disjoncteur restait le seul rempart, c'est-à-dire L0 employé
|
||
comme régulateur au lieu de dernier recours. Qui lira cette ligne comme une régression lira
|
||
le symptôme, pas la cause.
|
||
* AUCUN TEST DE CE DÉPÔT NE POUVAIT LE TROUVER. Le mock pose `currentPhaseB` =
|
||
`currentPowerPhaseB / 230` : les deux états y sont cohérents par construction, donc lire
|
||
l'un ou l'autre y donne le même nombre. Il a fallu désaccorder les états à la main, ce
|
||
qu'aucun chemin normal du mock ne permet (`testPhaseAllowanceReadsTheStateItTests`, qui
|
||
échoue sans le correctif en annonçant 25 A de place libre sur une phase qui en tire 20).
|
||
L'angle mort est nommé comme une CLASSE dans SAFETY.md : l'installation de référence
|
||
n'exerce pas le cas, et une suite verte ne dit alors rien sur lui.
|
||
* Un défaut voisin de la même fonction est consigné SANS être corrigé : `voltage` est déclarée
|
||
hors des trois blocs et mutée par celui de la phase A, donc la tension de A peut servir à
|
||
calculer B. Plus contraint, aucun relevé ne le montre, et il aura son propre correctif isolé.
|
||
* Suite complète : simulation 51/51, charging 48/48 (identique à la référence, ligne à ligne),
|
||
loadmodel 21/21, spotmarket 32/32, doxygen 0 avertissement.
|
||
|
||
-- Patrick Schurig <etm.schurig@gmail.com> Sun, 30 Aug 2026 19:40:00 +0200
|
||
|
||
powersync-energy-plugin-nymea (1.15.2+etm33) trixie; urgency=medium
|
||
|
||
* R7 — `decision.level` : le motif hérité dit de quel NIVEAU il parle. `decision` reste au
|
||
niveau de la charge, mais il reflète le motif d'UN niveau ; sans le dire, chaque client
|
||
réinventerait sa règle de fusion (« le motif du dernier niveau servi ? du premier refusé ? »)
|
||
et deux écrans afficheraient deux motifs pour le même cycle. Il n'y a qu'un niveau
|
||
aujourd'hui et la question ne se pose pas — c'est précisément pourquoi le champ arrive
|
||
maintenant : quand elle se posera, les clients le liront déjà.
|
||
* PRÉSENT EXACTEMENT QUAND levels[] L'EST. En mode dégradé il n'y a pas de passe, donc pas de
|
||
niveau dont le motif parlerait : ni levels[], ni decision.level.
|
||
* R1 — l'ABSENCE et le ZÉRO disent deux choses différentes, et les deux moitiés sont épinglées.
|
||
Niveau absent = passe non parcourue ; niveau à targetW 0 = passe parcourue, rien à prendre.
|
||
Un zéro forgé détruirait la première information, une absence la seconde. Aujourd'hui la
|
||
passe unique est toujours parcourue dès qu'une charge est arbitrée : c'est le mode dégradé
|
||
qui porte le cas « aucune passe ».
|
||
* testLevelAbsenceAndZeroDifferInMeaning, vérifié échouant DANS LES DEUX SENS — un niveau à
|
||
zéro omis, et un decision.level publié sans levels[].
|
||
* LM-1014 — UN CLIENT TESTE LA PRÉSENCE D'UN CHAMP, JAMAIS UNE VERSION. Règle posée par
|
||
l'agent app en déplaçant son exception EV_GRID_START plutôt qu'en la retirant : elle survit
|
||
là où la charge utile n'a pas de counts. Ce qui rend une capacité détectable est la charge
|
||
utile, pas un numéro — une box se rétrograde, un paquet se reconstruit, une branche se
|
||
déploie hors séquence. Portée en tête d'INTERFACE.md : les versions y datent les changements,
|
||
elles ne sont pas des conditions à tester.
|
||
* R5, R6 et R8 NE SONT PAS REPOUSSÉES, elles sont sans objet : vérifié que ECO_FLOOR_MET,
|
||
ECO_FLOOR_GRID et DRAW_CAP n'existent pas dans le catalogue de motifs. Écrire aujourd'hui la
|
||
garde de R6 poserait un invariant sur un code que rien n'émet — un test toujours vert, donc
|
||
un test qui ne protège rien.
|
||
* Suite complète : simulation 35/35, charging 17/17, loadmodel 20/20, spotmarket 7/7. amd64
|
||
0 erreur / 0 avertissement, doxygen 0 avertissement.
|
||
|
||
-- Patrick Schurig <etm.schurig@gmail.com> Sun, 30 Aug 2026 17:10:00 +0200
|
||
|
||
powersync-energy-plugin-nymea (1.15.2+etm32) trixie; urgency=medium
|
||
|
||
* R3 — `counts` : chaque watt publié tombe dans exactement un compteur, et le mapping est
|
||
PUBLIÉ. Chaque niveau porte { destination → watts }, avec Σ counts == targetW toujours.
|
||
Clés opaques (budget.allocatedW, draw.committedW), jamais recyclées pour un sens nouveau —
|
||
recycler ferait ranger des watts dans le mauvais compteur sans qu'aucune erreur ne le
|
||
signale, même règle que les numéros de règle retirés.
|
||
* Publié TOUJOURS : même à une seule destination, même à zéro. Un `counts` vide satisferait
|
||
l'identité en perdant la destination, sur le cas le plus fréquent. Zéro AVEC sa destination
|
||
dit « cette grandeur existe et vaut 0 » ; l'absence dirait « elle n'existe pas ».
|
||
* funding et counts ne se dérivent PAS l'un de l'autre : funding dit l'ORIGINE des watts,
|
||
counts le REGISTRE où ils sont comptés. Sur un EV_GRID_START l'un vaut « grid » quand
|
||
l'autre porte deux destinations.
|
||
* L'IDENTITÉ DE RÉCONCILIATION NE PASSE PLUS PAR funding : elle somme des compteurs, plus des
|
||
origines. EV_GRID_START y entre sans exception — 900 W à l'allocation, 480 W au soutirage.
|
||
* LA GARANTIE EST UNE MACHINE, PAS SEPT SITES DE CONSTRUCTION. testCountsSumToTarget relit la
|
||
CHARGE UTILE — toutes les charges, trois régimes dont l'import où tout vaut 0 — et vérifie
|
||
l'identité, la non-vacuité de counts et l'appartenance des clés au catalogue. Un huitième
|
||
site qui oublierait counts échoue ici, pas en relecture de code.
|
||
* Et la contre-épreuve a trouvé un trou DANS LE TEST : amputer le partage d'EV_GRID_START le
|
||
laissait passer, la borne auto-provisionnée en queue ne voyant jamais de budget. Corrigé —
|
||
rang 1, régime EV_GRID_START en premier (une fois la borne en charge, son recrédit porte le
|
||
budget au-dessus du plancher et le régime ne peut plus se produire), plus une assertion qui
|
||
EXIGE que le régime ait été exercé.
|
||
* GetChargingInfos FILTRE enfin sur evChargerId. Le paramètre était déclaré au schéma,
|
||
documenté « for all or a single EV charger », et Q_UNUSED : accepté, sans effet, sans
|
||
erreur. Des trois issues c'était la pire — un paramètre absent se voit, un paramètre refusé
|
||
se voit, un paramètre ignoré ne se voit pas. Identifiant inconnu → liste VIDE, jamais une
|
||
erreur : toute borne configurée ayant une entrée, la liste vide est non ambiguë.
|
||
* Balayage du namespace : sur 20 méthodes, 9 déclarent des paramètres et GetChargingInfos
|
||
était la SEULE à en ignorer un. La famille était petite.
|
||
* Suite complète : simulation 34/34, charging 17/17, loadmodel 20/20, spotmarket 7/7. amd64
|
||
0 erreur / 0 avertissement, doxygen 0 avertissement.
|
||
|
||
-- Patrick Schurig <etm.schurig@gmail.com> Sun, 30 Aug 2026 14:20:00 +0200
|
||
|
||
powersync-energy-plugin-nymea (1.15.2+etm31) trixie; urgency=medium
|
||
|
||
* `draw.committedW` — LE REGISTRE DES WATTS ACHETÉS, préalable à R3. Publié dans la MÊME TRAME
|
||
que `budget` : les deux décrivent le même cycle, et les lire à deux instants ferait comparer
|
||
deux mondes — l'identité de réconciliation redeviendrait temporelle par un autre chemin.
|
||
* SEUL, et délibérément. `authorisedW`, `remainingW`, `binding` et `perPhaseBound` viendront
|
||
avec le transport du §14a : ABSENTS, jamais à 0. Un `authorisedW: 0` assorti d'un `binding`
|
||
par défaut ferait afficher un plafond de soutirage qui n'existe pas, avec sa source — un
|
||
chiffre crédible et faux, c'est-à-dire la faute même que cet objet doit rendre impossible.
|
||
* budget.evReservedW N'A PLUS QU'UNE NATURE. Il recevait la part réseau d'EV_GRID_START, une
|
||
vraie tranche d'allocation, EN PLUS de la correction A (commandé non encore mesuré). Cette
|
||
part est passée dans draw.committedW. Sans ce déménagement, aucune définition du champ
|
||
n'aurait été vraie pour ses deux termes.
|
||
* Et sa définition est écrite, parce que l'app l'affichait sous « Réservé au véhicule » : c'est
|
||
une CORRECTION du budget de surplus, pas une réservation. Elle tend vers 0 à mesure que la
|
||
mesure rattrape la commande, sans qu'aucune décision n'ait changé — un rattrapage de latence.
|
||
Le contrat dit désormais : ne l'additionner à rien, il ne se réconcilie jamais.
|
||
* EV_GRID_START CESSE D'ÊTRE UNE EXCEPTION. Ses deux moitiés tombent maintenant dans des
|
||
compteurs de même nature — 900 W dans budget.allocatedW, 480 W dans draw.committedW — et
|
||
budgetW + gridW == allocatedW se lit sans entorse. Le partage demeure, c'est un fait ; ce qui
|
||
disparaît est l'exception à connaître.
|
||
* Ce que draw compte : ce que le moteur a DÉCIDÉ d'acheter selon son modèle de financement, pas
|
||
ce que le compteur verra passer. Pour une action du proxy — échéance, tarif dynamique —
|
||
toute l'allocation est comptée comme achetée : l'échéance décide de charger qu'il y ait du
|
||
soleil ou non, et aucun partage n'est calculé. Là où le moteur SAIT partager, il partage.
|
||
* Publication seule : aucune arithmétique de budget ne change. Vérifié avant l'écriture — la
|
||
ligne qui alimentait evReservedW depuis la cascade n'avait aucun retour dans le calcul.
|
||
* Suite complète : simulation 33/33, charging 16/16, loadmodel 20/20, spotmarket 7/7. amd64
|
||
0 erreur / 0 avertissement, doxygen 0 avertissement.
|
||
|
||
-- Patrick Schurig <etm.schurig@gmail.com> Sun, 30 Aug 2026 11:30:00 +0200
|
||
|
||
powersync-energy-plugin-nymea (1.15.2+etm30) trixie; urgency=medium
|
||
|
||
* R2 — LE FINANCEMENT DESCEND AU NIVEAU. `funding` était par CHARGE ; il vit désormais dans
|
||
`levels[]`. Au §10 la même charge sera financée au réseau pour son plancher éco et au surplus
|
||
pour son confort DANS LE MÊME CYCLE : un financement par charge y devient indécidable, et
|
||
c'est lui qui porte l'identité de réconciliation de l'app.
|
||
* Déplacé MAINTENANT bien que le §10 ne soit pas écrit : c'est la seule des huit exigences de
|
||
la maquette qui soit un changement de FORME d'un champ existant, donc la seule qui se
|
||
renchérisse à chaque écran qui s'y appuie. Les sept autres attendent sans se renchérir.
|
||
* UN SEUL NIVEAU aujourd'hui, et c'est « comfort » : la passe unique d'aujourd'hui est celle
|
||
qui servira le confort depuis le surplus. La passe éco est la nouveauté du lot, pas la passe
|
||
existante rebaptisée. Une charge sans obligation éco n'aura jamais d'entrée « eco » à zéro —
|
||
l'absence est l'information (R1).
|
||
* `funding` N'EST PAS conservé en résumé sur la charge. Le cas mixte est justement celui qui le
|
||
rendrait indécidable : « mixte » serait un troisième code que personne n'a demandé, « grid si
|
||
l'un l'est » une convention que chaque client redériverait autrement. Deux endroits pour un
|
||
fait divergent. Le motif `decision` reste, lui, parce qu'on saura dire de quel niveau il
|
||
parle (R7) — on garde un champ hérité quand on peut en dire quelque chose de VRAI, jamais par
|
||
symétrie.
|
||
* L'IDENTITÉ DE RÉCONCILIATION est écrite dans INTERFACE.md et non laissée à reconstruire :
|
||
somme sur les charges, somme sur les niveaux dont funding == "surplus", de niveau.targetW ==
|
||
budget.allocatedW. Elle a changé de forme avec R2 ; un client qui la reconstruirait depuis
|
||
l'ancienne divergerait au premier cas mixte, c'est-à-dire au premier cycle du §10.
|
||
* L'unique exception est publiée : EV_GRID_START partage une allocation entre deux compteurs,
|
||
et sa contribution au surplus est decision.params.budgetW, pas targetW. C'est ce que R3
|
||
généralisera ; d'ici là l'exception est dans le contrat, pas dans la tête du lecteur.
|
||
* `levels[]` absent = box antérieure, OU mode dégradé — sans plan, il n'y a pas de financement.
|
||
* Rupture assumée pour un client antérieur qui lirait `entry.funding` : même arbitrage que
|
||
measuredW → measurement, pas d'alias, deux noms pour une chose divergent.
|
||
* Suite complète : simulation 33/33, charging 16/16, loadmodel 20/20, spotmarket 7/7. amd64
|
||
0 erreur / 0 avertissement, doxygen 0 avertissement.
|
||
|
||
-- Patrick Schurig <etm.schurig@gmail.com> Sat, 29 Aug 2026 16:40:00 +0200
|
||
|
||
powersync-energy-plugin-nymea (1.15.2+etm29) trixie; urgency=medium
|
||
|
||
* ECS-111 DÉFINI ET CORRIGÉ — un relais déclaré qui manque au registre nymea rend la charge
|
||
INDISPONIBLE. Le comportement était « non défini » depuis l'écriture de la spec :
|
||
findConfiguredThing rendait nullptr, writeRelay loguait et continuait, telemetry() SAUTAIT le
|
||
relais. Deux issues, toutes deux fausses — ou bien plus aucun relais actif ne mesurait et
|
||
l'on retombait sur le nominal du PALIER ENTIER, part du relais fantôme comprise ; ou bien des
|
||
survivants mesuraient et l'on publiait une somme SOUS-ÉVALUÉE.
|
||
* Ce n'était pas un défaut d'affichage. telemetry().currentPowerW alimente le recrédit
|
||
anti-clignotement, donc le BUDGET : l'arbitre se trompait sur le surplus disponible pendant
|
||
qu'available restait vrai. Un mensonge qui décide, pas un mensonge qui s'affiche.
|
||
* La règle retenue est celle de la famille : available faux, faultCode THING_MISSING, et le
|
||
traitement d'ECS-410 — sortie de l'ARBITRAGE, maintien dans la COMPTABILITÉ, figée à la
|
||
puissance crue engagée. La puissance est donc RETIRÉE du budget des charges suivantes au lieu
|
||
de leur être recréditée. Aucune somme partielle n'est jamais publiée.
|
||
* Écartée : « compter 0 W et servir le reste ». Un relais absent du registre est un Thing
|
||
supprimé ou un plugin qui ne le voit plus — on ne sait pas dans quel état est le contact, et
|
||
servir le reste reviendrait à commander une charge dont on ne connaît plus la configuration
|
||
matérielle.
|
||
* Jugé sur TOUS les relais déclarés, jamais sur ceux du palier courant : au palier 0 aucun n'est
|
||
fermé, et juger là-dessus ferait basculer la disponibilité au rythme du thermostat. Même règle
|
||
que deviceMeasurement(), qui traitait DÉJÀ le cas correctement en rendant source: "none" —
|
||
aligner l'ancien sur le neuf, pas inventer une politique.
|
||
* THING_MISSING prime sur WRITE_FAILED dans runtimeView() : un relais absent EXPLIQUE les
|
||
écritures qui échouent, et annoncer WRITE_FAILED enverrait l'opérateur au bus quand la cause
|
||
est dans la configuration. Corollaire écrit : ClearLoadFault lève le défaut d'ÉCRITURE mais ne
|
||
fait pas exister un Thing — les deux causes se lisent séparément.
|
||
* testEcsPartialFailure a dû être ajusté, et c'est instructif : il se servait d'un relais JAMAIS
|
||
CONFIGURÉ comme raccourci pour forcer un échec d'écriture. Le raccourci a désormais un sens
|
||
propre. Le test distingue maintenant les deux — le défaut collant se lève, le câblage
|
||
manquant non.
|
||
* Éprouvé par testVanishedRelayFreezesTheLoad, vérifié échouant sans le correctif, dont la
|
||
troisième assertion est celle qui compte : ce que le fantôme laissait aux charges SUIVANTES.
|
||
* TROUVÉ PAR LA LECTURE D'UNE RÈGLE, ni par un test ni par le banc. ECS-111 dormait dans une
|
||
table de statut supprimée depuis, et est ressortie dans les quatorze règles « spécifiées
|
||
seules en territoire livré » que tools/gen-rules.py a listées. Premier défaut réel exhumé par
|
||
le chantier de documentation.
|
||
* Passe aussi : trois citations de règles ajoutées (LM-400, LM-301-b, LM-600), quatre règles
|
||
passées en cadrage, et la classe « cadrage » elle-même — 70 « spécifiée seule » au départ,
|
||
33 aujourd'hui. Suite complète : simulation 33/33, charging 16/16, loadmodel 20/20,
|
||
spotmarket 7/7. amd64 0 erreur / 0 avertissement, doxygen 0 avertissement.
|
||
|
||
-- Patrick Schurig <etm.schurig@gmail.com> Sat, 29 Aug 2026 15:05:00 +0200
|
||
|
||
powersync-energy-plugin-nymea (1.15.2+etm28) trixie; urgency=medium
|
||
|
||
* LE RATTRAPAGE DE CONFIGURATION EST CYCLIQUE (LM-1209-e). provisionEvChargerConfig() vivait
|
||
dans la branche « l'adaptateur vient d'être créé » de syncAdapters(). Or l'adaptateur est
|
||
construit depuis le THING : il survit à la suppression de l'entrée de configuration. Une
|
||
entrée supprimée depuis l'app n'était donc JAMAIS recréée — mesuré sur .75 le 2026-08-29,
|
||
entrée retirée, Thing vivant, rien en 140 s.
|
||
* Ce que cela produisait est le trou de 3g-2 rouvert par le côté client : la borne restait
|
||
ARBITRÉE mais ABSENTE de GetLoadConfig, à un rang de queue non réglable (evPriority() →
|
||
1000). Elle consommait du budget sans que personne puisse la classer, et rien ne le disait.
|
||
Seul un redémarrage de nymead réparait.
|
||
* La condition devient un ÉTAT et non un ÉVÉNEMENT : « cette borne n'a pas d'entrée » au lieu
|
||
de « l'adaptateur vient de naître ». Un rattrapage dont la condition est un événement ne
|
||
rattrape rien, puisqu'il ne se rejoue pas. Même distinction qu'entre ECS-411 — relire l'état
|
||
réel des relais — et l'hypothèse de départ qu'il a remplacée.
|
||
* Une entrée recréée revient en rankOrigin "auto", et c'est juste : personne ne l'a classée,
|
||
son rang est de nouveau un défaut. Le rang posé par l'installateur est PERDU avec l'entrée.
|
||
Supprimer une entrée n'est donc pas un moyen de retirer une borne de l'arbitrage — elle
|
||
revient, déclassée, en queue et avec son badge. enabled: false reste la voie, et conserve le
|
||
rang. La documentation promettait l'inverse à l'app : corrigée.
|
||
* Le REFUS de provisionnement n'est annoncé QU'UNE FOIS par borne. Le rattrapage étant
|
||
cyclique, répéter l'avertissement en produirait un par minute et pour toujours — règle déjà
|
||
posée pour les charges sans décision dans buildTelemetry(). Le drapeau tombe au premier
|
||
succès : un défaut qui se résout se redit s'il revient.
|
||
* LM-1209-d — le rang est un ordre TOTAL, les domaines ne le partitionnent pas. L'insertion en
|
||
queue entrelace les domaines ; exiger la contiguïté serait contradictoire avec la raison même
|
||
de LM-1209, puisque insérer une charge d'un autre domaine décalerait les suivantes. Grouper à
|
||
l'affichage oui, à l'écriture non. Aucun code : c'est une règle de contrat.
|
||
* INTERFACE.md porte désormais en tête l'avertissement de sonde : un refus arrive en
|
||
status: "success", le verdict étant dans params.energyError.
|
||
* Éprouvé par testDeletedEvChargerConfigComesBack — suppression, rattrapage au cycle suivant,
|
||
retour en queue et en "auto", et le cas négatif : le rattrapage ne se rejoue pas quand
|
||
l'entrée existe, sans quoi il réécrirait la config à chaque cycle et réarmerait les verrous.
|
||
Vérifié échouant avec l'ancien comportement. Suite complète : simulation 32/32, charging
|
||
16/16, loadmodel 20/20, spotmarket 7/7. amd64 0 erreur / 0 avertissement.
|
||
|
||
-- Patrick Schurig <etm.schurig@gmail.com> Sat, 29 Aug 2026 11:20:00 +0200
|
||
|
||
powersync-energy-plugin-nymea (1.15.2+etm27) trixie; urgency=medium
|
||
|
||
* L'AUTO-PROVISIONNEMENT REMPLIT UN TROU, IL NE DÉCIDE PAS (LM-1209). Une borne détectée
|
||
entrait EN TÊTE de la liste — rang min − 1, écrasé à 1 — donc à égalité avec la charge que
|
||
l'installateur avait posée là exprès. Elle entre désormais EN QUEUE : 1 + max(rangs). Le rang
|
||
est un choix de l'installateur ; une détection ne réordonne pas ce qu'un humain a décidé.
|
||
* Ne contredit pas le défaut « le VE d'abord » : celui-là décrit une MISE EN SERVICE INITIALE,
|
||
où personne n'a encore rien classé — liste vide, max + 1 vaut 1. Un ajout ultérieur est une
|
||
autre situation : il existe un classement et il fait autorité.
|
||
* Queue de LISTE et non de DOMAINE, et c'est plus fidèle au principe : les domaines ne sont pas
|
||
contigus, donc « juste après le dernier ev » exigerait de pousser une charge d'un autre
|
||
domaine. Comme max(domaine) ≤ max(liste), la queue de liste est toujours au moins aussi
|
||
reculée — elle coïncide quand le domaine occupe la fin, et sinon place la nouvelle charge
|
||
derrière tout le monde plutôt que de l'intercaler devant une charge classée.
|
||
* UNICITÉ DE RANG GARANTIE, PAS SUPPOSÉE (LM-1209-a). SetLoadConfig refuse en bloc un ensemble
|
||
à rangs doublés. Ce que l'égalité coûte n'est PAS le déterminisme — le waterfall trie sur
|
||
(rang, identifiant) depuis 3g-2, l'ordre est stable — c'est qu'il devient NON RÉGLABLE : il
|
||
se décide sur des identifiants, donc sur personne. Gouvernabilité, pas déterminisme.
|
||
* UNE VÉRIFICATION QUI ÉCARTE ET UNE QUI REFUSE NE PEUVENT PAS ÊTRE LA MÊME FONCTION
|
||
(LM-1209-a-bis, règle générale). load() applique validateSet() en ÉCARTANT l'entrée fautive
|
||
— bon geste pour un conflit d'organe. Y ajouter l'unicité de rang aurait fait perdre des
|
||
charges au démarrage sur une configuration héritée : disparues de la configuration ET de
|
||
l'arbitrage, sans applySafeState, sur la foi d'une règle resserrée après coup. validateRanks()
|
||
est donc séparée et n'est appelée que par setConfigs(). Migration : au chargement, doublon
|
||
signalé bruyamment et CONSERVÉ ; la première écriture cliente répare.
|
||
* LA CRÉATION SE DIT : o:rankOrigin, énumération fermée "auto" | "user", absente par défaut
|
||
(LM-1209-c). Une entrée créée d'office prend une place dans l'ordre de service ; sans marque,
|
||
c'est un changement d'arbitrage silencieux, et le journal ne suffit pas — l'app ne le lit pas
|
||
et c'est elle qui montre le classement.
|
||
* Énumération et non booléen : `rankIsDefault: false` AFFIRMERAIT que le rang a été choisi,
|
||
alors que c'est ce qu'on ignore d'une configuration héritée. L'absence doit porter le « on ne
|
||
sait pas », et un booléen ne sait pas se taire. Même motif que measurement.source.
|
||
* L'EFFACEMENT PORTE SUR LE RANG, JAMAIS SUR L'ÉCRITURE REÇUE. SetLoadConfig remplace en bloc :
|
||
réordonner une AUTRE charge renvoie forcément l'entrée de celle-ci, et l'effacer à cette
|
||
occasion perdrait une marque encore vraie. L'omission de la clé ne lève rien ; pour lever
|
||
sans déplacer, un client envoie "user". Un "auto" fabriqué par un client est refusé, mais
|
||
l'aller-retour verbatim reste licite — sans quoi la neutralité Get→Set serait rompue.
|
||
* Éprouvé par testAutoProvisionedRankGoesToTheQueue (simulation), vérifié échouant avec
|
||
l'insertion en tête, et par testRankUniquenessRefusesWriteButNeverDiscards +
|
||
testRankOriginFollowsTheRankNotTheWrite (loadmodel) — dont la moitié qui ne se voit pas :
|
||
qu'un chargement CONSERVE ce qu'il aurait pu écarter. Suite complète : simulation 31/31,
|
||
charging 16/16, loadmodel 20/20, spotmarket 7/7. amd64 0 erreur / 0 avertissement.
|
||
|
||
-- Patrick Schurig <etm.schurig@gmail.com> Fri, 28 Aug 2026 17:40:00 +0200
|
||
|
||
powersync-energy-plugin-nymea (1.15.2+etm26) trixie; urgency=medium
|
||
|
||
* LA MESURE D'UNE CHARGE SE LIT SUR L'APPAREIL PILOTÉ (LM-1105). Il fallait jusqu'ici désigner
|
||
une borne comme SON PROPRE compteur dans meterThingId pour qu'elle publie ce qu'elle mesure
|
||
déjà. Ce n'est devinable par personne — c'est de la configuration demandée à l'installateur
|
||
pour compenser ce que le moteur peut déduire. Quand le Thing piloté porte lui-même l'état de
|
||
puissance, le moteur le lit, sans configuration.
|
||
* La règle porte sur le THING, pas sur le mécanisme : certains powerswitch portent currentPower
|
||
et RelayRouter sait déjà les sommer. Et le test se pose sur la CLASSE, sur TOUS les Things
|
||
déclarés — jugé sur les relais actifs, il répondrait « non mesurable » au palier 0, et le bloc
|
||
de mesure apparaîtrait et disparaîtrait au rythme du thermostat.
|
||
* Le compteur explicite reste PRIORITAIRE, et pas d'abord parce qu'il mesure mieux : un réglage
|
||
sans effet est pire que pas de réglage. La priorité porte sur la SOURCE, pas sur la valeur —
|
||
compteur déclaré injoignable, on retombe sur l'appareil plutôt que de taire une mesure qu'on a.
|
||
* measuredW DISPARAÎT au profit de measurement {source: meter|device|none, o:powerW} (LM-1106).
|
||
Le moteur portait déjà trois régimes sous un seul nom et jetait la distinction : evcharger
|
||
rend une mesure, relay-router une mesure OU le palier nominal — le booléen `metered` était
|
||
calculé puis jeté — et sg-ready une puissance DÉCLARÉE, jamais mesurée. Le régime se PUBLIE,
|
||
il ne se déduit pas d'une valeur.
|
||
* `none` est un état POSITIF, et powerW y est OMIS : la puissance nominale reste lisible dans
|
||
mechanism.*, elle ne se fait pas passer pour une mesure. Un écart commandé↔mesuré fabriqué
|
||
depuis un nominal serait identiquement nul à tous les cycles — un « tout concorde » permanent,
|
||
corollaire LM-104.
|
||
* Deux affinements trouvés en implantant. LM-1105-a : une mesure PARTIELLE n'est pas une mesure
|
||
— trois relais dont un seul mesure annonceraient 500 W là où 1 500 W coulent. LM-1105-b : la
|
||
règle présuppose que le Thing piloté est TRAVERSÉ par la puissance ; un contact SG-Ready est
|
||
un contact de SIGNAL, et sg-ready est donc `none` par CONSTRUCTION, même sur des relais qui
|
||
mesurent. C'est le seul mécanisme où meterThingId reste la seule voie.
|
||
* ILoadAdapter::deviceMeasurement() est PURE VIRTUELLE, comme runtimeView() : un mécanisme
|
||
ajouté qui hériterait d'un « non mesurable » par défaut sortirait de la mesure sans bruit.
|
||
* Ne touche PAS ce que l'arbitre lit : LM-1101 tient, LoadTelemetry::currentPowerW est inchangé,
|
||
et le recrédit anti-clignotement n'a jamais transité par meterThingId.
|
||
* Éprouvé par testMeasurementSourceIsPublished — trois sources, palier 0, PAC en `none`, et les
|
||
deux règles de priorité ; la règle « capacité jugée sur la classe » vérifiée échouant quand on
|
||
la juge sur les relais actifs. Suite complète : simulation 30/30, charging 16/16, loadmodel
|
||
18/18, spotmarket 7/7. amd64 0 erreur / 0 avertissement.
|
||
|
||
-- Patrick Schurig <etm.schurig@gmail.com> Fri, 28 Aug 2026 16:05:00 +0200
|
||
|
||
powersync-energy-plugin-nymea (1.15.2+etm25) trixie; urgency=medium
|
||
|
||
* UNE BORNE SANS VOITURE ASSIGNÉE RESTE ARBITRÉE (LM-1208-b). Le cas de l'invité : au banc le
|
||
2026-08-28, la V2C tirait 3,9 kW et l'arbitre l'ignorait — evParticipatesInWaterfall()
|
||
reprenait du proxy un critère « voiture assignée » qui n'a de sens que là-bas. Le waterfall
|
||
n'a besoin ni du SoC, ni de la capacité, ni d'une échéance pour allouer du surplus.
|
||
* Trois raisons de le retirer, la troisième décisive : une voiture d'invité ne sera jamais
|
||
déclarée et la borne cesserait d'être arbitrée précisément quand elle consomme ; le critère
|
||
n'est pas vérifiable, l'assignation étant déclarée à la main ; et il CONTREDIT LM-1206, qui
|
||
donne le rang et le plancher à la borne, pas au véhicule. Ce qu'on perd sans voiture est une
|
||
FONCTION — échéance et cible d'énergie, restées au proxy — pas l'arbitrage.
|
||
* Restent deux exclusions, et elles ne disent pas la même chose : mode manuel (une INTENTION —
|
||
l'utilisateur pilote) et aucun véhicule branché (un CONSTAT — rien à consommer).
|
||
* SANS VOITURE, LE NOMBRE DE PHASES VIENT DE LA BORNE (LM-1208-c). Retirer le critère ne
|
||
suffisait pas, et ce qui manquait ne se voyait pas : le proxy ne PRÉPARE pas une borne sans
|
||
voiture, elle entre donc dans le waterfall sans ChargingProcessInfo — et l'accesseur rendait
|
||
un objet par DÉFAUT dont les valeurs sont plausibles, une phase et pas de bascule. Une borne
|
||
triphasée aurait été annoncée monophasée, budget divisé par trois : plafond 3 680 W au lieu
|
||
de 11 040, 16 A commandés au lieu de 9, 11 kW tirés d'une enveloppe qui en comptait 3,7.
|
||
* D'où internalHasProcessInfo() : « cette borne a-t-elle été préparée ce cycle » devient une
|
||
question POSABLE au lieu d'être déduite d'une valeur crédible. Sans ChargingProcessInfo on
|
||
ne bascule pas — basculer suppose de savoir ce que la voiture accepte — et on retient le
|
||
phaseCount de la borne. Hypothèse conservatrice au sens de LM-104 : elle surestime.
|
||
* Éprouvé par testChargerWithoutCarIsArbitrated, vérifié échouant sans le garde-fou (3 680 W
|
||
alloués au lieu de 6 000). Suite complète : simulation 29/29, charging 16/16, loadmodel
|
||
18/18, spotmarket 7/7. amd64 0 erreur / 0 avertissement.
|
||
|
||
-- Patrick Schurig <etm.schurig@gmail.com> Fri, 28 Aug 2026 14:20:00 +0200
|
||
|
||
powersync-energy-plugin-nymea (1.15.2+etm24) trixie; urgency=medium
|
||
|
||
* UNE BORNE SANS VÉHICULE BRANCHÉ EST HORS ARBITRAGE. Trouvé au banc en exerçant +etm23 :
|
||
une wallbox en Eco, voiture assignée, mais pluggedIn: false, s'est vu allouer 4 853 W
|
||
qu'elle ne pouvait pas tirer (currentPower: 0, charging: false). La PAC derrière elle n'a
|
||
survécu que sur son propre recrédit. Trois cycles consécutifs, 2 350 / 4 853 / 5 464 W
|
||
retirés du budget et donnés à personne.
|
||
* La cause est un filtre d'entrée incomplet : evParticipatesInWaterfall() reprenait celui du
|
||
proxy — voiture assignée, mode non manuel — sans pluggedIn. Le proxy n'en avait pas
|
||
besoin, son allocation n'étant pas une cascade : gaspiller ne privait personne. En cascade,
|
||
si. L'asymétrie était déjà dans le code — la boucle EV du scheduler, qui sert les
|
||
financements réseau, teste pluggedIn depuis toujours.
|
||
* Hors arbitrage, et non « servie à 0 avec un motif » : une borne débranchée ne consomme
|
||
rien, sa consommation nulle est déjà au compteur racine, et lui donner un rang la ferait
|
||
figurer dans la cascade pour n'y rien faire. Même traitement que les deux autres
|
||
exclusions, journal compris.
|
||
* Défaut de 3g-1, révélé par 3g-2 parce que la décision atteint enfin le matériel — c'est le
|
||
cinquième de cette famille, et le premier trouvé par la machine plutôt que par les tests.
|
||
Détail et mesures : docs/RELEVE_3g2.md §3.1.
|
||
* Éprouvé par testUnpluggedChargerTakesNoBudget, vérifié échouant sans le correctif. Suite
|
||
complète : simulation 28/28, charging 16/16, loadmodel 16/16, spotmarket 7/7.
|
||
|
||
-- Patrick Schurig <etm.schurig@gmail.com> Thu, 27 Aug 2026 13:30:00 +0200
|
||
|
||
powersync-energy-plugin-nymea (1.15.2+etm23) trixie; urgency=medium
|
||
|
||
* 3g-2 — UNE CHARGE, UN COMMANDEUR. C'est le défaut le plus grave qu'avait révélé le passage
|
||
machine du 2026-08-26 : adjustEvChargers() re-décidait le surplus APRÈS le waterfall et
|
||
passait outre le refus de l'arbitre. La décision de l'arbitre n'atteignait donc RIEN — elle
|
||
était publiée, juste, et une milliseconde plus tard le proxy commandait autre chose. Corrigé
|
||
des deux côtés : les actions EV du waterfall sont dispatchées vers EvAdapter::applyAction()
|
||
(elles ne l'étaient pas du tout), et les bornes ainsi commandées sortent du chemin proxy par
|
||
deux crochets virtuels posés dans l'amont. Le jeu des bornes commandées est NOMMÉ par le
|
||
scheduler (Plan::waterfallEvIds), jamais redéduit d'un motif ou d'un financement.
|
||
* evFloorW() ne multiplie plus par acquisitionTolerance. Ce sont deux grandeurs différentes,
|
||
confondues en écrivant la fonction : le plancher est ce que la borne EXIGE pour démarrer, la
|
||
tolérance est la part de ce plancher qu'on accepte de ne pas couvrir. Le banc a montré le
|
||
coût de la confusion : 2 070 W annoncés pour une borne qui en exige 4 140, waterfall qui
|
||
alloue 2 828 W, adaptateur qui refuse d'enclencher sous 6 A, et 2 828 W retirés du budget
|
||
pour personne.
|
||
* NOUVEAU MOTIF EV_GRID_START {budgetW, floorW, gridW} — la tolérance devient VISIBLE. Une
|
||
borne peut démarrer en soutirant au réseau ; le comportement existe depuis toujours chez le
|
||
proxy, où rien ne le nommait. L'action porte funding=Grid et sa comptabilité se PARTAGE :
|
||
budgetW dans budget.allocatedW, gridW dans budget.evReservedW, budgetW + gridW ==
|
||
allocatedW. Brèche assumée dans le financement réseau que DESIGN_3g §3.1 déférait à 3f, et
|
||
bornée par construction : surplus réel exigé, au plus UNE borne par cycle, sous le plafond
|
||
de la protection de surcharge. Détail : docs/DESIGN_3g.md §3.1-bis.
|
||
* NOUVEAU MOTIF PHASE_LIMIT {limitW, requiredW} — quand c'est la limite de phase qui interdit
|
||
et non le budget. Les deux phrases envoient l'installateur vers des gestes opposés.
|
||
* LE RANG D'UNE BORNE EST CONFIGURABLE, et il vit là où vivent tous les rangs :
|
||
LoadConfig.priority (LM-1206/LM-1207). Pas de second système de priorité — un classement
|
||
propre aux bornes ne saurait pas exprimer « VE1 > ECS > VE2 ». Toute borne détectée reçoit
|
||
d'office une entrée LoadConfig de mécanisme « evcharger », portant le rang et RIEN d'autre :
|
||
ses limites viennent du Thing et changent avec la voiture branchée. Création journalisée,
|
||
une ligne par borne, à la création seulement.
|
||
* CORRIGE UNE RUPTURE D'INVARIANT DE +etm21/+etm22, non signalée à l'époque : en faisant
|
||
entrer les bornes dans loads[], 3g-1 publiait des charges qui ne figuraient dans AUCUNE
|
||
GetLoadConfig. C'est exactement la charge fantôme que le lot B-bis avait supprimée,
|
||
réapparue par l'autre bout — et l'app s'appuie sur « loads[] ⊆ GetLoadConfig » pour résoudre
|
||
libellé, domaine et rang. L'auto-provisionnement le rétablit par construction.
|
||
* L'entrée de configuration d'une borne SURVIT à la disparition de son Thing : borne
|
||
remplacée, ThingId changé, appareil déposé. Elle reste publiée available: false /
|
||
faultCode: THING_MISSING / motif LOAD_UNAVAILABLE, et ne retient aucun budget. La supprimer
|
||
d'office rendrait « borne remplacée » indistinguable de « borne jamais configurée ».
|
||
* Tri du waterfall rendu TOTAL : à rang égal, l'identifiant départage. Deux bornes à
|
||
priority = 100 partageaient la même clé et std::sort n'est pas stable — laquelle démarrait
|
||
n'était pas reproductible d'un cycle à l'autre.
|
||
* CHANGEMENT DE COMPORTEMENT — la réserve de stockage ne mordait que sur les AUGMENTATIONS.
|
||
Le recrédit anti-clignotement rendait à chaque charge sa propre consommation, y compris sous
|
||
la réserve : une charge en marche le restait, pendant que le motif publiait « aucune charge
|
||
n'est servie ». Sous la réserve le budget est une décision de retenir, pas une mesure : le
|
||
recrédit y est supprimé (LM-1203-b, « annulé, pas réduit »), et withheldW chiffre désormais
|
||
les deux termes retenus — le surplus annulé ET la consommation propre de la charge.
|
||
* Le nombre de phases visé est décidé avec la voiture branchée. canSetPhaseCount() ne dit que
|
||
« la borne porte l'état desiredPhaseCount » ; la vraie question est tranchée par le manager
|
||
(canSwitchPhaseCount, min/maxPossiblePhaseCount). Les deux avaient divergé en 3g-1, et une
|
||
borne monophasée se voyait viser 3 phases — plancher publié triplé, et sous surcharge 3 × 6 A
|
||
commandés là où le proxy commandait 1 × 6 A. La référence de la bascule redevient aussi le
|
||
courant POSSIBLE (courant tiré + surplus) et non le courant tiré.
|
||
* L'écrêtage par la limite de phase, perdu en 3g-1 avec planSurplusCharging(), est rétabli —
|
||
et DÉCLARÉ, dans LoadContext.declared.maxPowerW : le waterfall ne peut plus allouer au-delà
|
||
de ce que le branchement supporte. Il ne descend jamais sous le plancher de la borne, parce
|
||
que couper est le geste de la couche L4 et non celui de la stratégie.
|
||
* [upstream-fix] SetChargingInfo annonçait « Only given properties will be set, others will be
|
||
untouched ». C'est FAUX : l'objet remplace le stocké, et un champ writable omis retombe sur
|
||
son défaut — targetPercentage à 80, repeatDays vide, endDateTime absent — sous
|
||
EnergyErrorNoError. Constaté sur le banc le 2026-08-26. La description dit désormais la
|
||
vérité, avec la liste des défauts et le geste correct (lire, modifier, renvoyer complet), et
|
||
un test épingle le comportement qu'elle annonce.
|
||
* [upstream-fix] prepareInformation() repliait sur ChargingModeEco tout mode sans échéance
|
||
valide, alors que le repli ne concerne que les modes qui en EXIGENT une. Deux conséquences :
|
||
ChargingModeEcoWithMinCurrent était réécrit et persisté au cycle suivant — le mode « éco +
|
||
courant minimum garanti » ne survivait jamais, et la branche d'adjustEvChargers() qui
|
||
l'applique était morte ; et ChargingModeEco était « replié » sur lui-même à chaque cycle,
|
||
avec écriture disque et notification chargingInfoChanged sans qu'aucune valeur ne change.
|
||
* Tests : la suite de simulation ne COMPILAIT plus depuis 639689b (constructeur
|
||
EtmVariableLoadAdapter, paramètre minPowerW non répercuté sur cinq sites d'appel) — donc
|
||
plus personne ne l'exécutait. Réparée, puis étendue. Le catalogue de motifs avait dérivé au
|
||
passage : BATTERY_RESERVE et BELOW_MIN_POWER étaient entrés sans ligne de rendu attendue,
|
||
donc leur TEXTE — celui sur lequel le banc s'appuie — n'était éprouvé par rien.
|
||
* Suite complète verte : simulation 27/27 (dont les 16 scénarios `run`), charging 16/16,
|
||
loadmodel 16/16, spotmarket 7/7. Build amd64 0 erreur / 0 warning. RIEN N'A TOURNÉ SUR
|
||
MACHINE : voir docs/RECAP_3g2.md pour ce qui reste à éprouver au banc.
|
||
|
||
-- Patrick Schurig <etm.schurig@gmail.com> Thu, 27 Aug 2026 11:00:00 +0200
|
||
|
||
powersync-energy-plugin-nymea (1.15.2+etm22) trixie; urgency=medium
|
||
|
||
* 3g-1, CE QUI MANQUAIT — les bornes étaient bien arbitrées, mais elles ne franchissaient
|
||
pas la frontière RPC. buildTelemetry() n'interrogeait que la table des charges de
|
||
configuration et sautait l'EV avec un commentaire devenu faux (« décidé en amont du
|
||
waterfall »). Résultat : le journal montrait les décisions des deux bornes, et
|
||
GetLoadTelemetry n'en montrait aucune. Le changement de contrat annoncé par +etm21 n'était
|
||
donc PAS livré, et le critère LM-1204-b était inéprouvable — son discriminant est le MOTIF
|
||
publié, précisément ce qui n'arrivait pas. Attrapé au premier passage machine.
|
||
* NOUVEAU CHAMP loads[].funding — « surplus » ou « grid ». Il devient nécessaire dès lors
|
||
que les bornes sont dans loads[] : une borne servie par la cascade est comptée dans
|
||
budget.allocatedW, une borne servie par l'échéance ou le tarif dynamique SOUTIRE au réseau
|
||
et vit dans budget.evReservedW. Sans ce champ, un client qui somme allocatedW trouve un
|
||
écart sans pouvoir dire s'il regarde un défaut ou une borne financée au réseau. Omis en
|
||
mode dégradé : sans plan, il n'y a pas de financement.
|
||
* Les bornes figurent aussi dans la télémétrie DÉGRADÉE. C'est là que savoir qu'une borne
|
||
existe et n'est plus pilotée vaut le plus ; l'omettre ferait lire « pas de borne ».
|
||
* Une borne SANS action reste hors du contrôle « charge arbitrée sans décision » : sans
|
||
voiture assignée ou en mode manuel, elle est hors arbitrage et n'en recevoir aucune est
|
||
correct. Avertir à chaque cycle sur une borne simplement débranchée noierait le signal.
|
||
* Rendu du motif BATTERY_RESERVE : « batterie à 50 %% » s'affichait tel quel. « %% » est une
|
||
échappée printf ; QString::arg ne l'interprète pas. Vu sur le banc à la première
|
||
observation de la règle.
|
||
* CONSTAT, non corrigé ici : le RANG d'une borne n'est pas configurable — descriptor() fixe
|
||
priority = 100 pour toutes. Deux bornes ont donc la même clé de tri, et std::sort n'est pas
|
||
stable : la cascade est correcte (une seule démarre, l'autre reçoit son motif) mais
|
||
LAQUELLE n'est ni réglable ni garanti reproductible. Demande un lot dédié, et ne doit pas
|
||
être présenté comme un réglage dans l'app avant.
|
||
* Code mort retiré : buildMinCurrentAction() et buildIdleAction() n'étaient plus appelés
|
||
depuis +etm21 mais restaient compilés, seuls porteurs de EV_ECO_MIN et EV_IDLE. Les trois
|
||
codes retirés (avec EV_SURPLUS) restent DÉCLARÉS et rendus pour qu'un journal antérieur ou
|
||
une box en retard sur son app reste lisible — mais plus rien ne les produit.
|
||
|
||
-- Patrick Schurig <etm.schurig@gmail.com> Wed, 26 Aug 2026 19:40:00 +0200
|
||
|
||
powersync-energy-plugin-nymea (1.15.2+etm21) trixie; urgency=medium
|
||
|
||
* 3g-1 — LA TRANSPLANTATION. Les bornes de recharge entrent dans le waterfall : elles
|
||
partagent le budget avec l'ECS et la PAC, décrémentent la cascade, et portent enfin un
|
||
rang qui a un sens. Il était décoratif depuis le début.
|
||
* Le défaut que cela corrige n'était pas théorique. planSurplusCharging() calculait
|
||
l'allocation UNE fois, avant la boucle, et ne la décrémentait jamais : chaque borne
|
||
recevait la TOTALITÉ du surplus, comme si elle était seule. Avec 4 kW et deux bornes, ce
|
||
n'était pas « 2 kW à chacune » mais « 4 kW à chacune » — et ce qui rattrapait n'était pas
|
||
l'arbitrage mais le compteur au cycle suivant, puis la protection de surcharge. Le système
|
||
convergeait par correction d'erreur, pas par décision.
|
||
* Trois motifs EV disparaissent, et ce sont des suppressions, pas des remplacements :
|
||
EV_SURPLUS devient SURPLUS_SETPOINT, EV_ECO_MIN devient le PLANCHER du §12 — la même idée
|
||
exprimée une seule fois pour tous les mécanismes — et EV_IDLE cède à BELOW_MIN_POWER /
|
||
SURPLUS_INSUFFICIENT, qui DISENT pourquoi la borne ne charge pas.
|
||
* Le nombre de phases est décidé AVANT l'allocation et réutilisé à l'exécution, parce que
|
||
c'est lui qui fixe le plancher : 6 A valent 1,4 kW en monophasé et 4,1 kW en triphasé.
|
||
Publier le plancher du nombre COURANT alors que l'exécution bascule vers un autre
|
||
produirait un défaut silencieux — le waterfall allouerait 2 kW en croyant démarrer une
|
||
borne qui en exige 4,1, et se croirait servi.
|
||
* La conversion watts → ampères descend dans l'adaptateur (règle 4 : l'adaptateur écrête,
|
||
il ne décide pas). Deux origines coexistent : le proxy remplit currentA, la cascade
|
||
remplit powerW. Le discriminant est currentA.
|
||
* evReservedW (correction A) est RESTREINT au financement réseau au lieu d'être supprimé :
|
||
une borne servie par la cascade décrémente déjà le budget par son allocation, la réserver
|
||
en plus la compterait deux fois. Échéance et spot market restent décidés en amont, donc
|
||
restent réservés.
|
||
* Restent au proxy, délibérément : l'échéance de départ (sa machinerie vient du SoC de la
|
||
voiture et n'a aucun équivalent ailleurs ; et la tenir revient à SOUTIRER, donc à franchir
|
||
la frontière du §10), le spot market, la protection de surcharge, l'estimation de SoC.
|
||
Le proxy maigrit, il ne disparaît pas — c'est une étape assumée, pas un provisoire.
|
||
* Bornes sans voiture assignée ou en mode manuel : HORS arbitrage, pas rang 0. Leur
|
||
consommation est déjà au compteur racine, donc déjà dans le budget.
|
||
* ⚠️ CHANGEMENT DE CONTRAT — loads[] de GetLoadTelemetry contient désormais les bornes, et la
|
||
somme des allocations retombe sur budget.allocatedW, ce qui n'était pas vrai avant.
|
||
* Correction du motif BATTERY_RESERVE, attrapée à sa PREMIÈRE observation en vrai : il était
|
||
publié même quand rien n'avait été retenu (surplus négatif), disant « une règle vous
|
||
bloque » là où la vérité était « il n'y a pas de surplus » — exactement la confusion qu'il
|
||
existe pour supprimer, retournée. Il est désormais conditionné à withheldW > 0.
|
||
|
||
-- Patrick Schurig <etm.schurig@gmail.com> Wed, 26 Aug 2026 18:00:00 +0200
|
||
|
||
powersync-energy-plugin-nymea (1.15.2+etm20) trixie; urgency=medium
|
||
|
||
* 3g-1, première moitié — la prise en compte du stockage monte dans le budget. Elle vivait
|
||
dans planSurplusCharging(), donc dans le chemin VE, par accident d'histoire : ce n'a jamais
|
||
été une règle « véhicule » mais une règle de BUDGET. Tant qu'elle y restait, une batterie
|
||
sous sa réserve empêchait de charger la voiture et laissait le chauffe-eau se servir.
|
||
* ⚠️ CHANGEMENT DE COMPORTEMENT pour l'ECS et la PAC, qui n'ont jamais connu cette règle.
|
||
Une installation qui servait ses charges sous le seuil de réserve ne le fera plus.
|
||
* ⚠️ DÉFAUT D'USINE ABAISSÉ : batteryLevelConsideration passe de 0,9 à 0,2. Le réglage
|
||
confondait une CIBLE et une RÉSERVE — une cible est ce qu'on aimerait atteindre, une
|
||
réserve ce sous quoi on ne descend pas, et la règle qu'il pilote a une sémantique de
|
||
réserve. Tant qu'elle ne touchait que le VE, 0,9 se défendait ; depuis qu'elle gouverne
|
||
toute l'installation, 0,9 gèle tout sous 90 % de SOC : ni ECS, ni PAC, ni borne. Chez un
|
||
client cela se manifeste comme un HEMS qui ne pilote plus rien, sans erreur et sans panne —
|
||
le pire mode de défaillance, parce qu'il ne se signale pas. Les installations existantes
|
||
conservent leur valeur enregistrée ; seul le défaut change.
|
||
* Motif BATTERY_RESERVE (socPercent, reservePercent, withheldW) — troisième terme de la
|
||
distinction déjà portée par BELOW_MIN_POWER : il n'y a rien / il y en a mais pas au bon
|
||
format / il y en a et une RÈGLE l'interdit. Sans lui, une installation qui exporte en plein
|
||
soleil n'enclenche rien et publie « surplus insuffisant » : l'installateur cherche une panne
|
||
qui n'existe pas. withheldW dit combien de surplus a été retenu — c'est ce chiffre, et non
|
||
le SOC, qui rend la cause lisible.
|
||
* Sous la réserve, le budget est ANNULÉ et non réduit (LM-1203-b). Servir « un peu »
|
||
retarderait la recharge du stockage sans démarrer personne, puisque les planchers du §12
|
||
mordent d'abord.
|
||
* ⚠️ CHANGEMENT DE CONTRAT — BATTERY_RESERVE est un code nouveau : un client sans clé de
|
||
rendu affichera son repli.
|
||
|
||
-- Patrick Schurig <etm.schurig@gmail.com> Wed, 26 Aug 2026 16:00:00 +0200
|
||
|
||
powersync-energy-plugin-nymea (1.15.2+etm19) trixie; urgency=medium
|
||
|
||
* §12 suite — BELOW_MIN_POWER étendu aux deux branches qui connaissaient la cause précise
|
||
et publiaient la générique. La distinction est « il n'y a pas assez » contre « il y en a,
|
||
mais pas au bon format », et elle décide du geste : attendre le soleil, ou relire une
|
||
fiche technique.
|
||
* Branche à paliers (relay-router, etmvariableload fixed) : un budget de 300 W devant un
|
||
premier palier de 500 W n'est pas un manque de surplus, c'est une granularité. Elle
|
||
publiait SURPLUS_INSUFFICIENT ; elle publie désormais BELOW_MIN_POWER avec le palier
|
||
hors d'atteinte.
|
||
* Branche sg-ready : elle publiait SG_NORMAL — correct sur le fond, mais son rendu français
|
||
disait « Surplus insuffisant (933 W) » devant un état 3 à 1 500 W. Le code publié n'a
|
||
jamais trompé l'app ; c'est l'installateur qui lit journalctl pendant une mise en service
|
||
qui l'était. Un budget positif sous l'état 3 donne maintenant BELOW_MIN_POWER, avec le
|
||
paramètre state qui NOMME l'état hors d'atteinte : dire « sous le plancher » sans dire
|
||
lequel remplacerait une phrase vague par une autre.
|
||
* SG_NORMAL ne sort donc plus que pour un budget nul ou négatif, et sa phrase devient
|
||
« Aucun surplus à allouer » — vraie dans ce seul cas, là où « surplus insuffisant »
|
||
couvrait aussi, et à tort, un budget de 933 W.
|
||
* SURPLUS_INSUFFICIENT est conservé tel quel pour budget ≤ 0 : il reste juste quand il n'y a
|
||
réellement rien.
|
||
* ⚠️ CHANGEMENT DE CONTRAT — BELOW_MIN_POWER est un code nouveau, et son paramètre state est
|
||
optionnel. Un client sans clé de rendu affichera son repli.
|
||
* Le cas EV porte le même défaut (EV_IDLE dit « pas de surplus » là où la borne a un plancher
|
||
de 6 A), mais son site est le proxy amont, que la transplantation 3g remplace. Corrigé là
|
||
plutôt qu'ici, sans quoi on écrirait deux fois la même règle.
|
||
|
||
-- Patrick Schurig <etm.schurig@gmail.com> Wed, 26 Aug 2026 14:30:00 +0200
|
||
|
||
powersync-energy-plugin-nymea (1.15.2+etm18) trixie; urgency=medium
|
||
|
||
* Lot E §12 — le plancher de modulation. Le domaine d'une consigne cesse d'être
|
||
[0, maxPowerW] pour devenir {0} ∪ [minPowerW, maxPowerW], un ensemble DISCONTINU —
|
||
parce que c'est ce que fait le matériel : une borne ne charge pas sous 6 A, une PAC n'a
|
||
pas d'« état 3 à moitié ».
|
||
* LM-1203, la règle qui fait le travail : sous le plancher, la charge reçoit 0 W et le
|
||
budget PASSE à la suivante. Sans elle, une charge de rang 1 à plancher élevé retiendrait
|
||
en otage un budget qu'elle ne peut pas utiliser, et tout le waterfall en aval resterait à
|
||
sec pendant qu'elle-même ne démarre pas.
|
||
* Le plancher est DÉCLARÉ pour etmvariableload dynamic et DÉRIVÉ ailleurs — plus petit
|
||
palier non nul chez relay-router, plus petite estimation non nulle chez sg-ready. Même
|
||
règle que stagesW au lot D : ne pas déclarer ce qui se dérive. SetLoadConfig refuse donc
|
||
o:minPowerW sur relay-router et en mode fixed, et le refuse aussi s'il dépasse maxPowerW —
|
||
le domaine serait réduit à {0} et la charge ne démarrerait jamais sans que rien ne dise
|
||
pourquoi.
|
||
* Motif BELOW_MIN_POWER (params budgetW, minPowerW), distinct de SURPLUS_INSUFFICIENT.
|
||
Les confondre enverrait diagnostiquer un manque de soleil là où il faut lire une fiche
|
||
technique : le budget peut être confortable et rester sous le minimum de CE matériel.
|
||
C'est ECS-309 appliqué — le motif nomme le mécanisme qui a produit l'issue.
|
||
* Le waterfall satisfaisait DÉJÀ LM-1203 pour les charges à paliers : le plus haut palier
|
||
≤ budget vaut 0 quand le budget est sous le premier palier, et le budget cascade. Le trou
|
||
n'était que dans la branche continue, où qBound(0, budget, max) acceptait n'importe quelle
|
||
miette.
|
||
* tests/auto/loadmodel : 15/15 en 3 ms.
|
||
|
||
-- Patrick Schurig <etm.schurig@gmail.com> Wed, 26 Aug 2026 13:00:00 +0200
|
||
|
||
powersync-energy-plugin-nymea (1.15.2+etm17) trixie; urgency=medium
|
||
|
||
* Lot E §11 — compteur et sonde par charge. Deux champs optionnels de même forme sur
|
||
LoadConfig : o:meterThingId et o:sensorThingId. Ils décrivent ce qu'on OBSERVE d'une
|
||
charge, jamais ce qu'on lui commande.
|
||
* L'arbitre ne s'en sert pas pour décider, et c'est vrai par CONSTRUCTION : les adaptateurs
|
||
ne voient même pas ces Things. Ils sont résolus au moment de publier la télémétrie
|
||
(attachMeasurements()), et nulle part ailleurs — la mesure ne traverse aucun chemin
|
||
d'arbitrage.
|
||
* Le compteur VÉRIFIE, il ne budgète pas. La mesure n'est pas injectée au budget : elle est
|
||
en retard sur la commande (scrutation Modbus, moyennage du compteur) et l'y mettre brute
|
||
réintroduirait l'oscillation que le recrédit anti-clignotement a supprimée ; et la charge
|
||
est déjà comptée dans le compteur racine, dont celui-ci n'est qu'une décomposition.
|
||
measuredW est donc publié À CÔTÉ de allocatedW, jamais fondu dedans, sans quoi la somme
|
||
des allocations cesserait d'être réconciliable avec budget.allocatedW.
|
||
* La sonde est en lecture seule. Son usage immédiat est le diagnostic de mise en service ;
|
||
son usage durable est de constituer l'historique (température, puissance, heure) sans
|
||
lequel les seuils éco/confort du §10 ne se régleraient jamais autrement qu'au doigt
|
||
mouillé. C'est la raison pour laquelle ce champ existe AVANT d'avoir un consommateur.
|
||
* Ni le compteur ni la sonde ne sont exclusifs, contrairement à relays[]. LM-201 (« un canal
|
||
de commande, une charge ») vaut pour ce qui commande ; ceux-ci mesurent. claimedThingIds()
|
||
les exclut, l'exclusion est désormais commentée sur place pour qu'on ne la « corrige » pas,
|
||
et deux charges peuvent partager une sonde de pièce ou un compteur divisionnaire.
|
||
* Aucun contrôle d'existence à l'écriture : un ThingId encore inconnu est accepté. Refuser
|
||
imposerait un ordre aux opérations — déclarer le compteur avant la charge — que rien ne
|
||
justifie. Un id qui ne résout pas produit une absence de mesure, pas une config invalide.
|
||
* Omis quand vides, jamais une chaîne vide persistée — même règle que domain.
|
||
* tests/auto/loadmodel : 12/12, dont l'aller-retour des deux champs, leur absence de
|
||
claimedThingIds(), le partage d'un compteur entre deux charges, et le fait que les
|
||
rattacher ne reconstruit rien.
|
||
|
||
-- Patrick Schurig <etm.schurig@gmail.com> Wed, 26 Aug 2026 11:00:00 +0200
|
||
|
||
powersync-energy-plugin-nymea (1.15.2+etm16) trixie; urgency=medium
|
||
|
||
* Lot D — les constats de l'agent app, dans l'ordre de ce qu'ils coûtent.
|
||
* Renommer une charge atteint enfin l'adaptateur. updateSoftConfig() ne portait que
|
||
priority et needs : un changement de libellé seul était compté « inchangée », l'adaptateur
|
||
gardait son ancien nom, et le journal de la box contredisait GetLoadConfig jusqu'à la
|
||
prochaine reconstruction. Pour qui suit journalctl pendant une mise en service, les deux
|
||
sources se contredisaient. `label` entre dans la signature et dans le prédicat `soft` de
|
||
l'arbitre — sans entrer dans sameHardware() : c'est une mise à jour EN PLACE, pas une
|
||
reconstruction. La vraie prise était le Q_UNUSED de SgReadyAdapter, hérité d'avant le lot
|
||
B-bis : sans lui, renommer la PAC — le cas même qu'on allait tester — n'aurait rien changé.
|
||
* relays[] n'est plus comparé par index. Réordonner les mêmes relais aux mêmes puissances
|
||
reconstruisait la charge : contacts ouverts par l'état sûr, verrous minOn/minOff réarmés à
|
||
froid. Un installateur qui remonte une ligne dans l'app ne s'attend pas à couper sa charge,
|
||
et annuler son geste en coûtait une SECONDE. Les comparer comme un ensemble aurait été faux
|
||
dans l'autre sens : à somme égale la table retient la première combinaison rencontrée, donc
|
||
échanger deux relais de MÊME puissance change le contact qui sert ce palier — autre
|
||
contacteur qui s'use, autre résistance qui chauffe. sameHardware() compare désormais la
|
||
TABLE DÉRIVÉE : deux listes qui produisent les mêmes paliers avec les mêmes contacts sont
|
||
matériellement identiques, par construction et non par pari.
|
||
* Les paliers atteignables sont publiés — mechanism.stagesW, en télémétrie. Ils ne sont
|
||
déclarés nulle part : ils se dérivent des relais, et sans publication un client qui veut les
|
||
montrer doit réimplémenter la combinatoire. Seconde implémentation, divergence certaine à la
|
||
première correction d'un seul côté, et c'est le client qui aurait tort sans le savoir.
|
||
Publiés là et PAS dans GetLoadConfig, à dessein : la télémétrie n'a aucun chemin d'écriture,
|
||
donc « lecture seule » y est vrai par construction, alors que GetLoadConfig aurait cassé
|
||
l'aller-retour neutre dont l'app dépend pour écrire — elle renvoie verbatim ce qu'elle a lu,
|
||
le champ serait revenu dans SetLoadConfig, et un refus en bloc aurait emporté toutes les
|
||
charges.
|
||
* RelayRouter::deriveStages() : implémentation UNIQUE de la combinatoire, partagée par la
|
||
construction du routeur, la comparaison matérielle et la publication. C'était le fond de la
|
||
demande : une seule règle, donc pas de divergence possible.
|
||
* sameHardware() passe en public. Ce prédicat décide si une écriture coûte un geste MATÉRIEL
|
||
ou une mise à jour en place — un élément de contrat, que le client annonce à l'installateur
|
||
avant d'enregistrer. Statique et sans état.
|
||
* tests/auto/loadmodel — cible neuve, volontairement légère : aucun serveur nymea, 2 ms. Elle
|
||
a attrapé un défaut de la correction elle-même avant le banc : la première version de
|
||
RelayStageTable::operator== comparait les listes de ThingIds, donc voyait une différence
|
||
entre fermer {K1,K2} et fermer {K2,K1}. Même geste électrique, deux listes. Les contacts
|
||
d'un palier se comparent maintenant comme un ensemble.
|
||
* tools/repair_energylogs.py — répare les cumuls d'énergie empoisonnés par une lecture de
|
||
compteur absurde. N'est PAS un correctif de ce plugin : la cause est extérieure (SunSpec
|
||
lisant les float32 Fronius aux mots permutés, corrigé par etm-powersync-plugins-modbus
|
||
d08cf50, daté 05:30:14 UTC — la seconde même du premier échantillon empoisonné du banc). Ce
|
||
que le script rattrape est la SÉQUELLE : EnergyManagerImpl accumule sans contrôle de
|
||
plausibilité dans un compteur persistant, et à 7,5e33 kWh le plus petit incrément
|
||
représentable en double vaut 1,67e18 — le compteur n'était pas seulement faux, il était
|
||
GELÉ, et rechargé tel quel à chaque démarrage. Appliqué sur .75 : 62 726 anomalies → 0,
|
||
accumulation reprise et vérifiée en direct.
|
||
|
||
-- Patrick Schurig <etm.schurig@gmail.com> Wed, 26 Aug 2026 08:00:00 +0200
|
||
|
||
powersync-energy-plugin-nymea (1.15.2+etm15) trixie; urgency=medium
|
||
|
||
* La PAC entre en configuration : le bloc d'enregistrement codé en dur d'energypluginnymea.cpp
|
||
est SUPPRIMÉ, avec registerSgReadyAdapter(), la table m_sgReadyAdapters et claimedRelays().
|
||
Il n'existe plus aucun chemin d'enregistrement en dur : m_loadAdapters est la table unique
|
||
des charges arbitrées, et toutes ses entrées viennent de LoadConfigStore. La PAC du banc est
|
||
devenue une LoadConfig « sg-ready » ordinaire — nommable, classable, rangeable,
|
||
désactivable — comme n'importe quelle autre charge.
|
||
* Contrat : loads[] de GetLoadTelemetry est désormais INCLUS dans GetLoadConfig, et c'est un
|
||
invariant de CONSTRUCTION, pas une discipline. La règle de détection publiée à +etm14 — « un
|
||
loadId sans équivalent dans GetLoadConfig est une charge non configurable » — est RETIRÉE
|
||
d'INTERFACE.md : elle n'avait qu'un porteur, pac-terrain, qui vient de disparaître. Aucun
|
||
champ « configurable: false » n'a été ajouté : il n'aurait plus rien à signaler. Un client
|
||
qui a déjà implémenté la règle peut la garder sans dommage, elle ne se déclenchera plus.
|
||
L'inclusion ne vaut que dans ce sens : GetLoadConfig peut contenir des charges absentes de
|
||
loads[] — celles en enabled:false, exclues de l'arbitrage (contrat §9). Une charge configurée
|
||
absente de loads[] se lit « désactivée », jamais « perdue ».
|
||
* SÉCURITÉ — repli L2 d'une PAC de configuration : trou ouvert par LM-300, refermé ici. Le
|
||
repli du mode dégradé envoyait un Setpoint(0) à toute la table des charges pilotées et un
|
||
State(2) à une table séparée, réservée à la PAC codée en dur. Or une PAC construite depuis la
|
||
CONFIG vit dans la première table : elle recevait donc un Setpoint, que SgReadyAdapter
|
||
rejette d'entrée (kind non supporté) — et rien d'autre. Sous compteur muet, cette PAC-là
|
||
restait en état 4 (forcé) indéfiniment, alors que SAFETY.md §L2 exige l'état 2. Le défaut
|
||
était invisible : le motif DEGRADED_L2 apparaissait correctement dans la télémétrie, seule
|
||
l'ÉCRITURE manquait. Le repli lit maintenant supportedKinds déclaré par l'adaptateur et
|
||
refuse explicitement ce qu'il ne sait pas replier. Règle portée en LM-301-b : dès qu'un
|
||
mécanisme rejoint la table commune, tout code qui FABRIQUE une action doit lire
|
||
supportedKinds au lieu de présumer du kind à partir de la table où la charge se trouve.
|
||
* ECS-411 appliqué au SG-Ready : l'état de départ est RELU sur les contacts, plus supposé à 2.
|
||
Tant que la PAC était codée en dur et construite une fois au démarrage, l'hypothèse se voyait
|
||
peu ; depuis qu'elle vient de la configuration, elle est reconstruite à chaque changement de
|
||
matériel. Le vrai piège est le corollaire LM-104 : applyAction(état 2) sur un adaptateur qui
|
||
se CROIT en 2 est idempotent, donc muet, donc les contacts restent en 4 — l'hypothèse de
|
||
prudence devient un masquage de panne. ECS-411-b : le relevé publie ce qui a été LU, contact
|
||
par contact, avec l'issue — y compris quand l'issue est l'état neutre, qui est justement
|
||
celui qu'on vérifie après un démarrage — et passe en avertissement si un contact est
|
||
injoignable (supposé fermé, LM-104).
|
||
* ECS-412 appliqué au minStateHold : un m_lastSwitch nul vaut « commutation venant d'avoir
|
||
lieu », jamais « jamais commuté ». Sans cet armement, un redémarrage de nymead pendant que le
|
||
compresseur tourne autoriserait une coupure immédiate — du court-cycling provoqué par un
|
||
redémarrage de service, exactement ce que le verrou existe pour empêcher. L'armement est
|
||
PARESSEUX : applyAction() estampille au premier « now » reçu, refusé ou non, et le verrou
|
||
expire ensuite normalement. Jamais permanent — c'est le blocage circulaire corrigé côté
|
||
routeur le 2026-08-09, où une charge démarrant contacts ouverts ne pouvait plus jamais
|
||
s'enclencher. runtimeView() adopte la MÊME convention que lockWindow(), de sorte que
|
||
l'affichage ne peut pas contredire le comportement. Conséquence à connaître : toute
|
||
reconstruction d'adaptateur réarme le verrou pour sa durée configurée (900 s sur une PAC
|
||
réelle) — sans effet au banc, réel sur une installation.
|
||
* Les puissances SG-Ready du banc ne sont PAS des données d'installation. estimatedPowerW
|
||
{3: 1500, 4: 3000} et minStateHoldS 300 sont reconduits du bloc supprimé, où ils décrivaient
|
||
une PAC INVENTÉE ; aucune PAC n'est sous tension sur K1/K2 (polarité des contacts encore non
|
||
vérifiée). Ils sont marqués PROVISOIRES ET NON MESURÉS dans docs/TEST_TERRAIN.md §1.d, à
|
||
remplacer par la machine réellement câblée. Ce n'est pas cosmétique : estimatedPowerW sert de
|
||
base au recrédit budget et au choix d'état, une estimation fausse décale l'allocation de tout
|
||
le waterfall en aval, pas seulement celle de la PAC.
|
||
* La suppression du bloc en dur ne passe pas par applySafeState(), et ne le peut pas : c'est un
|
||
événement de COMPILATION, pas d'exécution — ECS-413 ne s'applique qu'à un adaptateur qui sort
|
||
d'une configuration vivante. Le filet est procédural et documenté (TEST_TERRAIN.md §1.d) :
|
||
poser la config sg-ready AVANT d'installer ce paquet, sur le +etm14 en cours d'exécution, de
|
||
sorte que K1/K2 ne soient jamais sans propriétaire. Installer d'abord et configurer ensuite
|
||
laisse une fenêtre où plus personne ne commande les contacts : ils restent dans leur dernier
|
||
état commandé, possiblement fermés. enabled:false ne met pas davantage une charge en
|
||
sécurité : il la retire de la configuration.
|
||
* TEST_TERRAIN.md §1.d : la recette « coller un bloc C++ puis rebuild » est morte — elle ne
|
||
compilait plus, et elle produisait une charge arbitrée que GetLoadConfig ignorait. Remplacée
|
||
par la déclaration JSON-RPC des deux charges du banc en un SetLoadConfig. Changer un palier,
|
||
un rang ou un ThingId ne demande plus de cycle build cross-arm64 → .deb → deploy.sh.
|
||
* Tests : testSgReadyRestartRecovery (état relu sur contacts : 4, neutre, contact injoignable →
|
||
avertissement ; relevé contact par contact avec l'issue ; et la remise au neutre qui ÉCRIT au
|
||
lieu de se croire idempotente). testSgReadySurplus et le waterfall partagé passent par le
|
||
chemin de PRODUCTION (SetConfigs → changed → rebuild), plus par un enregistrement direct, et
|
||
lisent l'état de la PAC par la frontière publique — la télémétrie est vérifiée en même temps
|
||
que l'arbitrage. Volet 0 ajouté : l'armement à froid gèle, puis EXPIRE. testLoadTelemetryRpc
|
||
vérifie le repli L2 sur les CONTACTS et le mécanisme publié, pas sur le motif : c'est
|
||
précisément ce que le motif seul ne prouvait pas.
|
||
* Harnais de test — JournalCapture se chaînait SUR ELLE-MÊME. qInstallMessageHandler() rend le
|
||
handler courant ; à la DEUXIÈME capture vivante, ce handler est collecte() elle-même, si bien
|
||
que chaque message se rappelait indéfiniment en concaténant dans m_texte : récursion infinie
|
||
à croissance quadratique, donc OOM. Une seule capture vivante ne déclenchait rien, et le seul
|
||
test qui en ouvre deux est celui ajouté par ce lot. Le handler n'est désormais installé
|
||
qu'une fois, chaîné au handler d'ORIGINE, et toutes les captures vivantes reçoivent le
|
||
message — ce que le chaînage visait. À rapprocher de la contrainte d'exécution portée dans
|
||
AGENTS.md : la suite de simulation ne se lance JAMAIS en un seul processus, un serveur nymea
|
||
complet étant instancié par test sans que rien ne soit rendu entre deux.
|
||
* Harnais de test — tm et owner non initialisés (simulation.cpp, trois sites) : freshSetup()
|
||
peut sortir sur un QVERIFY en échec sans les renseigner, et ils étaient déréférencés juste
|
||
après. Initialisés par l'appelant et vérifiés après l'appel : un échec de test lisible au lieu
|
||
d'un comportement indéfini.
|
||
* Vérifié sur le banc .75 avant publication (docs/RELEVE_LOTBbis.md) : l'ordre de migration
|
||
exercé dans le bon sens (config posée sur le +etm14, qui cède les contacts, PUIS déploiement),
|
||
GetLoadConfig et GetLoadTelemetry concordants, applySafeState confirmé sur les contacts réels
|
||
au retrait d'une charge, et le verrou d'armement à froid qui EXPIRE (5 min 40 puis état 4 sous
|
||
surplus abondant) — l'armement est paresseux, jamais permanent.
|
||
|
||
-- Patrick Schurig <etm.schurig@gmail.com> Tue, 25 Aug 2026 16:30:00 +0200
|
||
|
||
powersync-energy-plugin-nymea (1.15.2+etm14) trixie; urgency=medium
|
||
|
||
* Contrat : loads[] de GetLoadTelemetry n'est PAS en correspondance 1:1 avec GetLoadConfig.
|
||
Une charge peut être arbitrée et publiée sans exister dans la configuration. Cas réel,
|
||
vérifié au banc : « pac-terrain » est la PAC SG-Ready CODÉE EN DUR d'energypluginnymea.cpp
|
||
(contacts K1/K2 = Relay4/Relay5, états {3: 1500 W, 4: 3000 W}, minStateHoldS 300), donc
|
||
pleinement arbitrée — elle consomme du budget, porte des verrous, apparaît en télémétrie —
|
||
et pourtant inconfigurable : ni renommage, ni rang, ni domain, ni enabled:false. Un client
|
||
qui suppose la correspondance affichera une charge sans nom ni domaine, ou cherchera une
|
||
configuration inexistante. INTERFACE.md porte désormais la règle de détection : un loadId
|
||
de loads[] sans équivalent dans GetLoadConfig est une charge NON CONFIGURABLE — afficher le
|
||
loadId, la traiter comme non classée, ne proposer aucun écran de réglage. Aucun champ n'est
|
||
ajouté à la charge utile pour le signaler : « configurable: false » préjugerait du sort de
|
||
cette charge, qui n'est pas tranché.
|
||
* Aucun changement de comportement moteur. Seuls la documentation de contrat, le journal de
|
||
vérification et les tests bougent — le binaire est fonctionnellement identique à +etm13,
|
||
dont il ne se distingue que par ce changelog. La version est incrémentée parce qu'un
|
||
artefact publié doit se relire dans sa propre source : livrer +etm13 avec un changelog
|
||
embarqué différent de celui du dépôt aurait recréé, en petit, la divergence que ce lot
|
||
passe son temps à fermer.
|
||
* Test : runtimeView() est SUBSTANTIELLE pour les quatre adaptateurs, et c'est prouvé par
|
||
mutation. La pure virtuelle force à RÉPONDRE, pas à répondre quelque chose : un
|
||
« return LoadRuntimeView{}; » compilerait, passerait le dispatch, et sortirait de la
|
||
télémétrie sans bruit — le client verrait une charge sans mécanisme ni verrou,
|
||
indistinguable d'une charge libre. testRuntimeViewIsSubstantial() amène chaque adaptateur
|
||
dans un état non trivial puis exige les valeurs runtime réelles (palier appliqué, consigne,
|
||
état normé, pourcentage dérivé à 75 % — ni 0, ni 100, ni la consigne), les verrous en
|
||
secondes restantes ET leur expiration, et les codes de défaut THING_MISSING et
|
||
UNUSABLE_ENCODING. Deux mutants « vue vide » ont été introduits puis retirés : le test
|
||
échoue sur chacun.
|
||
* Le déploiement sur box entre au dépôt : etm-powersync-deploy/deploy.sh, après l'incident du
|
||
2026-08-25 où .75 a été rétrogradée de +etm12 à +etm9. Trois causes composées — un glob sur
|
||
un répertoire de dépôt qui accumulait quatre paquets périmés, un dpkg -i qui autorise la
|
||
rétrogradation par défaut, et aucune relecture de version après coup. La recette fautive
|
||
figurait aussi dans docs/TEST_TERRAIN.md §b ; elle est remplacée. Le verrou décisif est
|
||
l'ARCHIVAGE avant dépôt : la règle sudoers de la box porte elle-même un « *.deb » et ne peut
|
||
pas être resserrée sans root, mais avec un seul paquet dans le répertoire ce glob ne peut
|
||
plus désigner qu'un fichier — y compris pour un geste fait à la main.
|
||
|
||
-- Patrick Schurig <etm.schurig@gmail.com> Tue, 25 Aug 2026 14:00:00 +0200
|
||
|
||
powersync-energy-plugin-nymea (1.15.2+etm13) trixie; urgency=medium
|
||
|
||
* Frontière de télémétrie d'arbitrage. Constat d'origine, vérifié au banc : 90 s d'écoute,
|
||
arbitre actif, ~2,9 kW produits et ~2 kW exportés — ZÉRO notification NymeaEnergy.*.
|
||
L'état runtime de l'arbitre (allocation, budget, verrous, paliers, disponibilité, motifs)
|
||
ne franchissait aucune frontière RPC : il vivait exclusivement dans qCInfo(dcNymeaEnergy).
|
||
Expliquer les décisions au client — promesse présente dans les deux versions de gamme —
|
||
n'avait donc aucun transport. Ajout de NymeaEnergy.GetLoadTelemetry et de sa notification
|
||
LoadTelemetryChanged, MÊME charge utile pour les deux.
|
||
* Aucune phrase composée par la box. La box transporte un CODE et des PARAMÈTRES ; la phrase
|
||
est fabriquée par le client, dans la langue de son utilisateur. Justification vérifiée :
|
||
la locale nymea est PAR CONNEXION (JSONRPC.Hello o:locale), donc deux utilisateurs d'une
|
||
même box peuvent avoir deux langues — impossible à servir avec une phrase composée en C++ ;
|
||
et NymeaEnergyJsonHandler est un JsonHandler sans pluginId, donc structurellement hors du
|
||
catalogue de traduction nymea : rien ne rattraperait une phrase française émise là.
|
||
* LoadAction::reason devient {code, params}. La ligne FRANÇAISE du journal est désormais
|
||
RENDUE à partir du code par une fonction de rendu unique (renderFr), et non maintenue en
|
||
parallèle : deux écritures parallèles divergeraient, et c'est le diagnostic terrain qui
|
||
deviendrait faux. Le catalogue de 19 codes est EXTRAIT des sites existants, pas inventé ;
|
||
les motifs d'ECS-309 et ECS-309-b sont repris mot pour mot. Prouvé par comparaison de
|
||
traces (1726 lignes de journal identiques en simulation) et par un test de rendu à table
|
||
d'or qui échoue au premier code ajouté sans phrase attendue.
|
||
* Le libellé de la charge n'entre PAS dans la charge utile RPC : c'est de la langue naturelle
|
||
saisie par l'installateur. Il est substitué au moment du RENDU, par l'appelant qui le
|
||
connaît. Le client résout le sien via GetLoadConfig.
|
||
* Les verrous sortent en SECONDES RESTANTES, pas en watts. lockMinPowerW/lockMaxPowerW sont
|
||
un artefact du mécanisme d'allocation, illisible pour un utilisateur. Ceci n'enfreint pas
|
||
ECS-306 : cette exigence interdit qu'un index de PALIER franchisse la frontière
|
||
d'ALLOCATION ; une valeur d'AFFICHAGE sortante est un autre chemin.
|
||
* Le mécanisme sort en union discriminée (relay | variable | sgReady), comme LoadConfig :
|
||
kind discrimine, chaque variante porte SA charge utile et seulement la sienne. Toutes les
|
||
clés internes optionnelles au schéma (règle LM-302). ILoadAdapter::runtimeView() est PURE
|
||
VIRTUELLE à dessein : un mécanisme ajouté qui hériterait d'un défaut vide sortirait de la
|
||
télémétrie sans bruit, et le client verrait une charge sans mécanisme ni verrou —
|
||
indistinguable d'une charge libre.
|
||
* budget publié : surplusW, evReservedW, recreditedW, allocatedW, remainingW, avec l'identité
|
||
surplus + recrédit − alloué == restant. recreditedW n'est pas décoratif : le waterfall rend
|
||
à chaque charge sa consommation de début de cycle avant d'arrondir (anti-clignotement,
|
||
correction B). Sans ce terme, l'identité serait fausse DÈS QU'UNE CHARGE CONSOMME, c'est-à-
|
||
dire dans le cas normal, et le client chercherait un écart inexplicable. Il rend aussi
|
||
lisible l'artefact de banc documenté dans AGENTS.md (relais sans mesure + rootmeter aveugle
|
||
→ recrédit du nominal commandé).
|
||
* Champs omis, jamais nuls (règle maison) : timestamp absent = aucun cycle d'arbitrage depuis
|
||
le démarrage — en forger un se lirait « cycle exécuté, rien à allouer », soit le mensonge
|
||
même que ce champ existe pour empêcher ; budget absent = mode dégradé L2, où la
|
||
planification est suspendue et où publier des zéros se lirait « rien à allouer ».
|
||
* Cadence : émission sur changement QUALITATIF ou variation d'allocation > 50 W, plus un
|
||
battement de cœur de 60 s même sans changement. C'est le battement qui rend un arbitre
|
||
FIGÉ détectable : les notifications continuent d'arriver tandis que le timestamp cesse
|
||
d'avancer. Sans lui, « installation stable » et « moteur arrêté » sont le même silence.
|
||
Toute notification relance le décompte : la cadence totale reste d'environ une par minute.
|
||
* GetChargingSchedules rend désormais degradedMode. Il était POUSSÉ en notification seule :
|
||
un client connecté après la bascule ne pouvait pas savoir dans quel état il se trouvait.
|
||
Règle : tout élément runtime a un Get* ET un *Changed, jamais une notification seule.
|
||
La bascule ET la sortie du mode dégradé publient aussi la télémétrie immédiatement.
|
||
* LoadConfig : champ optionnel domain, énumération FERMÉE (ecs · ev · heating · hvac ·
|
||
battery). PURE métadonnée d'intention — l'arbitre ne la lit pas, aucun comportement moteur
|
||
ne change ; elle enregistre le choix de l'installateur. Codes, jamais libellés. Valeur
|
||
absente = charge non classée : le plugin ne la range pas d'office, parce qu'un classement
|
||
inventé serait indistinguable d'un classement choisi. Valeur hors énumération rejetée en
|
||
bloc. Elle ne préempte pas la couche domaine à venir : elle en deviendra le discriminant.
|
||
* Règle 7-c, deux silences fermés : le retour idempotent du RelayRouter et celui du
|
||
SgReadyAdapter étaient MUETS, si bien que « consigne reçue, palier déjà bon » ne se
|
||
distinguait pas de « l'action n'est jamais parvenue à l'adaptateur » — et c'est justement
|
||
le cas ordinaire qu'on vient vérifier quand une charge ne bouge pas. Journalisés au même
|
||
niveau que la branche qui commute (7-d, symétrie).
|
||
* Tests : rendu français à table d'or (19 codes + repli d'un code hors catalogue),
|
||
télémétrie de bout en bout (notification non nulle, snapshot ≡ notification, union de
|
||
mécanisme entière, verrous en secondes, seuil qui ne mord pas sur 10 W, battement à
|
||
timestamp figé, reprise à froid du mode dégradé par les DEUX portes, contrôle mécanique
|
||
« zéro langue naturelle »), et domain (aller-retour, rejet hors énumération, charge non
|
||
classée, absence d'effet sur l'arbitrage). Suites : simulation 37/37, charging 46/46,
|
||
spotmarket 32/32.
|
||
|
||
-- Patrick Schurig <etm.schurig@gmail.com> Tue, 25 Aug 2026 12:00:00 +0200
|
||
|
||
powersync-energy-plugin-nymea (1.15.2+etm12) trixie; urgency=medium
|
||
|
||
* ECS-411-b — la reprise du palier au démarrage s'annonce toujours, et publie ce qu'elle
|
||
a LU : l'état de chaque relais avec sa puissance déclarée, le palier retenu, la branche
|
||
de déduction et la somme lue. La correspondance exacte ne journalisait qu'au palier non
|
||
nul ; au palier 0 — le cas de l'armement minOff, celui qu'on vient précisément observer
|
||
— le journal était muet, et « relais lus, tous ouverts » ne se distinguait pas de
|
||
« reprise non exécutée ». Une reprise avec relais injoignable passe en avertissement :
|
||
le palier déduit est plausible mais la lecture est incertaine.
|
||
* Défaut de fond trouvé PAR ce relevé, et corrigé. La puissance nominale de chaque relais
|
||
était redéduite des encodages solo de la table de paliers, or la déduplication par
|
||
puissance peut évincer un encodage solo : avec un câblage 500/1000/1500, le palier 1500
|
||
est encodé {500,1000} et le relais 1500 n'apparaît seul nulle part. Sa nominale valait
|
||
0, donc un démarrage sur ce seul relais annonçait palier 0 pendant que 1500 W
|
||
circulaient — l'annonce en sous-estimation qu'ECS-411 existe pour interdire, que le
|
||
silence au palier 0 couvrait. La nominale est prise à la déclaration.
|
||
* Règle 7-c d'AGENTS.md : un silence ne doit jamais être ambigu. Journaliser l'issue, pas
|
||
seulement l'issue remarquable ; journaliser ce qui a été lu, pas seulement ce qui en est
|
||
déduit. Trois autres endroits signalés dans spec_ecs.md, non corrigés.
|
||
* Protocole de mesure (RELEVE_ECS306 §3.0) : un état nymea n'émet changed() que si la
|
||
valeur bouge, donc une grandeur forcée constante déclenche le watchdog L2 en 90 s et
|
||
fait mesurer le watchdog au lieu de l'arbitrage. Constaté au banc.
|
||
* ECS-410-b — l'issue de ClearLoadFault est journalisée, et par l'ARBITRE. Elle distingue
|
||
trois cas : défaut levé, aucun défaut à lever, adaptateur introuvable. C'est le seul
|
||
levier de reprise à distance : ECS-410 pose un verrou collant que seuls ce RPC ou une
|
||
reconstruction lèvent, et sans issue lisible l'opérateur ne sait pas si son geste a agi
|
||
— il relance, puis conclut que le système est cassé. Écrite au niveau de l'arbitre, la
|
||
ligne couvre les quatre adaptateurs d'un coup et ne peut pas être oubliée par un
|
||
adaptateur futur. ILoadAdapter::clearFault() rend désormais un bool plutôt que void : le
|
||
type de retour force chaque implémentation à répondre à la question, là où une consigne
|
||
de journalisation se serait oubliée à la cinquième.
|
||
* Règle 7-d d'AGENTS.md : un refus DOIT être au moins aussi visible que l'application
|
||
correspondante. Le refus par verrou minStateHold de SgReadyAdapter était en qCDebug quand
|
||
l'application, dix lignes plus bas, était en qCInfo — un resserrement de la journalisation
|
||
aurait fait disparaître le refus AVANT le succès, ne laissant au journal que les décisions
|
||
abouties. Or un refus est plus informatif : il dit qu'une décision a été prise et n'a pas
|
||
été exécutée. Les autres qCDebug du moteur ont été vérifiés symétriques.
|
||
* Retour idempotent du routeur (relayrouter.cpp:228) : DIFFÉRÉ, et en qCDebug le jour venu.
|
||
C'est le cas le plus fréquent — plusieurs milliers de lignes par jour — et l'ambiguïté y
|
||
est partiellement couverte, le scheduler journalisant sa décision et l'armement à froid
|
||
s'exécutant avant ce retour.
|
||
* Suite complète : 112 tests, 0 échec. Tous les correctifs sont vérifiés échouant sans eux.
|
||
|
||
-- Patrick Schurig <etm.schurig@gmail.com> Thu, 13 Aug 2026 21:00:00 +0200
|
||
|
||
powersync-energy-plugin-nymea (1.15.2+etm11) trixie; urgency=medium
|
||
|
||
* ECS-309-b — le motif ne nomme un verrou que si ce verrou a déplacé la consigne. Le
|
||
critère était « consigne > budget », vrai dès que le budget passe sous zéro à palier
|
||
nul : 0 > -389. Le motif annonçait alors « Verrou minOn — chauffe-eau maintenue à 0 W
|
||
(puissance engagée, budget -389 W) » — un verrou qui ne mordait pas, sur une puissance
|
||
engagée nulle, quand le soutirage était la seule cause. Le critère compare désormais la
|
||
consigne appliquée à la consigne VOULUE avant écrêtage : relevée → minOn, rabaissée →
|
||
minOff, inchangée → le budget. Les deux branches se lisent sur le même axe.
|
||
* Constaté au banc le 2026-08-13 sur +etm10, sept fois en quatre heures, toujours au
|
||
cycle suivant un palier 0. Le défaut est antérieur à ECS-309, qui a ajouté la branche
|
||
minOff après lui sans voir que la première capturait déjà un cas étranger.
|
||
* Il ne s'agissait pas que de lisibilité. La branche minOn étant évaluée en premier, ce
|
||
défaut masquait le motif d'armement à froid d'ECS-412 chaque fois que le budget était
|
||
négatif au redémarrage — soit au fond du creux, précisément l'instant où il faut
|
||
redémarrer pour observer minOff. La campagne aurait mesuré le mauvais verrou.
|
||
* Suite complète : 112 tests, 0 échec. Le cas est vérifié échouant sans le correctif,
|
||
où il reproduit le texte exact relevé sur la machine.
|
||
|
||
-- Patrick Schurig <etm.schurig@gmail.com> Thu, 13 Aug 2026 08:00:00 +0200
|
||
|
||
powersync-energy-plugin-nymea (1.15.2+etm10) trixie; urgency=medium
|
||
|
||
* ECS-309 — motif de décision fidèle au mécanisme. Lorsqu'un verrou, et non le budget,
|
||
produit le palier appliqué, le decisionReason nomme le verrou et la contrainte qu'il
|
||
impose. Le chemin minOff — celui de l'armement à froid d'ECS-412 — publiait
|
||
« Surplus insuffisant (7706 W) » alors que 7706 W étaient précisément disponibles :
|
||
la décision était juste, le motif accusait le budget. Il cite maintenant le budget
|
||
DISPONIBLE, pour couper court à l'explication budgétaire.
|
||
* Les trois causes sont distinguables à la lecture : budget réellement insuffisant,
|
||
verrou minOn maintenant un palier au-dessus du budget, verrou minOff empêchant
|
||
l'enclenchement. Les cas minOn et minStateHold (PAC) étaient déjà fidèles.
|
||
* La règle 7 d'AGENTS.md exigeait un motif non vide ; ECS-309 l'étend de la présence à
|
||
la fidélité, et exige que les tests portent sur le mécanisme annoncé plutôt que sur
|
||
la non-vacuité de la chaîne — un test « motif non vide » n'aurait rien vu.
|
||
|
||
-- Patrick Schurig <etm.schurig@gmail.com> Tue, 11 Aug 2026 10:00:00 +0200
|
||
|
||
powersync-energy-plugin-nymea (1.15.2+etm9) trixie; urgency=medium
|
||
|
||
* Corrige le va-et-vient JSON-RPC cassé par l'union LM-300. GetLoadConfig sérialise
|
||
une charge via le méta-objet, donc toutes les propriétés déclarées — y compris un
|
||
« sgReady » VIDE sur une charge qui n'est pas SG-Ready. Le schéma SetLoadConfig
|
||
exigeant « states » dès que la clé est présente, nymea rejetait la requête AVANT le
|
||
handler : lire puis réécrire une configuration relay-router était impossible.
|
||
« states » et « minStateHoldS » passent en optionnels ; l'exigence réelle (states
|
||
non vide, état 2 présent) reste tenue par LoadConfig::isValid().
|
||
* Constaté au banc à la première tentative réelle de SetLoadConfig. Aucun test ne
|
||
rejouait le geste de l'app — lire, modifier un champ, réécrire. C'est désormais le
|
||
cas (testLoadConfigRpc §3bis, vérifié échouant sans le correctif).
|
||
|
||
-- Patrick Schurig <etm.schurig@gmail.com> Mon, 10 Aug 2026 09:00:00 +0200
|
||
|
||
powersync-energy-plugin-nymea (1.15.2+etm8) trixie; urgency=medium
|
||
|
||
* ECS-110-b — un Thing n'appartient qu'à UNE charge active. Le cas général n'était
|
||
pas couvert : deux charges de configuration pouvaient revendiquer le même relais
|
||
(déclaré à la fois dans la charge ECS et dans un état SG-Ready), soit deux
|
||
commandes contradictoires sur un organe et sa puissance comptée deux fois dans le
|
||
budget. LoadConfig::isValid() s'arrête au bord d'une charge ; la vérification
|
||
d'ensemble est portée par LoadConfigStore::validateSet().
|
||
* Deux doublons intra-charge également refusés : deux étages relay-router sur le même
|
||
Thing (le routeur les fusionnait en silence en annonçant le double de la puissance)
|
||
et un relais répété dans un même état SG-Ready. Un relais présent dans PLUSIEURS
|
||
états reste légitime — l'état 4 est l'union des états 1 et 3.
|
||
* Identifiants de charge dupliqués refusés : la seconde entrée était perdue en
|
||
silence à la construction. ThingIds normalisés via QUuid, « {uuid} » et « UUID »
|
||
désignant le même organe.
|
||
* Au chargement du fichier, une entrée en conflit est écartée plutôt que de perdre
|
||
toute la configuration : écarter une charge ne commande rien, la charger
|
||
commanderait faux.
|
||
|
||
-- Patrick Schurig <etm.schurig@gmail.com> Sun, 09 Aug 2026 15:00:00 +0200
|
||
|
||
powersync-energy-plugin-nymea (1.15.2+etm7) trixie; urgency=medium
|
||
|
||
* SG-Ready configurable — première implémentation réelle de spec_loadmodel.md §3.
|
||
LoadConfig devient une union discriminée par mécanisme (LM-300) : le champ
|
||
`adapter` est le discriminant, chaque mécanisme a une charge utile et valide la
|
||
sienne. `sg-ready` porte states[] { state, relays[], estimatedPowerW } et
|
||
minStateHoldS ; les paliers de la PAC ne sont plus codés en dur.
|
||
* Une configuration sg-ready dépourvue de l'état 2 est REFUSÉE, pas construite :
|
||
l'état 2 est le plancher de repli du mode dégradé L2 et de la désactivation.
|
||
Les deux Q_ASSERT de SgReadyAdapter, absents du binaire release, sont retirés au
|
||
profit de ce refus et d'un drapeau m_usable (ECS-110).
|
||
* La PAC de configuration vit dans la même table que les autres charges : elle
|
||
hérite d'ECS-412 (reconstruction incrémentale) et d'ECS-413 (état sûr = état 2)
|
||
sans code dédié.
|
||
* Garde de double propriété : si une PAC de configuration vise les contacts de la
|
||
PAC de banc enregistrée en dur, cette dernière est retirée — deux adaptateurs sur
|
||
un même contact commanderaient en contradiction et compteraient la charge deux
|
||
fois dans le budget.
|
||
* spec_loadmodel.md 0.1.2 — LM-104 : supposer conservateur pour annoncer, tenter
|
||
systématiquement pour agir. Vaut pour tout mécanisme, pas pour l'ECS seul.
|
||
|
||
-- Patrick Schurig <etm.schurig@gmail.com> Sun, 09 Aug 2026 12:00:00 +0200
|
||
|
||
powersync-energy-plugin-nymea (1.15.2+etm6) trixie; urgency=medium
|
||
|
||
* ECS-414 — l'échelle d'échec d'écriture (ECS-410) est généralisée à SgReadyAdapter et
|
||
EtmVariableLoadAdapter : suivi asynchrone des acquittements, échelle bornée, défaut
|
||
collant levé par NymeaEnergy.ClearLoadFault.
|
||
* Plancher propre à chaque adaptateur : consigne 0 W pour la charge continue, ÉTAT 2
|
||
pour la PAC SG-Ready — jamais « contacts ouverts », qui est une commande et peut valoir
|
||
le blocage selon l'encodage câblé.
|
||
* Le repli SG-Ready honore le même contrat d'atomicité que le chemin aller : il part du
|
||
motif de contacts RÉELLEMENT lu et ordonne les commutations par transientHarm, de sorte
|
||
que la récupération ne traverse pas le blocage. Il contourne minStateHoldS (900 s).
|
||
* Un contact injoignable est supposé FERMÉ pour la télémétrie (cohérence ECS-411) mais
|
||
TOUJOURS commandé : sans quoi l'hypothèse conservatrice masquerait l'échec.
|
||
* Test : testSgReadyPartialFailure. Suite 18/18.
|
||
|
||
-- Patrick Schurig <etm.schurig@gmail.com> Sun, 09 Aug 2026 21:00:00 +0200
|
||
|
||
powersync-energy-plugin-nymea (1.15.2+etm5) trixie; urgency=medium
|
||
|
||
* ECS-410 — échec d'écriture relais : l'adaptateur suit désormais l'acquittement de
|
||
chaque executeAction, sans jamais bloquer le cycle (AGENTS règle 5). Échelle bornée
|
||
à trois barreaux — cible, retour arrière, arrêt total — puis état de défaut COLLANT,
|
||
levé seulement par le nouvel appel NymeaEnergy.ClearLoadFault.
|
||
* Pendant une transition, la télémétrie annonce max(palier précédent, palier cible) :
|
||
ne jamais annoncer moins que ce qui peut être appliqué (même direction qu'ECS-411).
|
||
* LoadContext.available ajouté — il gouverne l'ALLOCATION, pas la COMPTABILITÉ : une
|
||
charge en défaut ne reçoit plus de consigne mais reste comptée au budget.
|
||
OPTIMIZER_PROTOCOL.md mis à jour dans le même lot.
|
||
* ECS-413 — désactiver une charge (enabled: false) la laisse dans l'état SÛR de son
|
||
adaptateur, appliqué AVANT destruction : relais ouverts pour un routeur, 0 W pour une
|
||
charge continue, état 2 pour une PAC SG-Ready — jamais le blocage. Un rebuild sans
|
||
désactivation ne coupe rien (ECS-412 préservé).
|
||
* Tests : testEcsPartialFailure, testEcsDisableLeavesSafeState. Suite 17/17.
|
||
|
||
-- Patrick Schurig <etm.schurig@gmail.com> Sun, 09 Aug 2026 18:00:00 +0200
|
||
|
||
powersync-energy-plugin-nymea (1.15.2+etm4) trixie; urgency=medium
|
||
|
||
* ECS-412 — correction d'un blocage circulaire du verrou au démarrage à froid,
|
||
constaté AU BANC (charge figée à 0 W sous 4 kW de surplus disponible).
|
||
Un horodatage de dernière commutation nul servait de sentinelle « écoulé = 0 »
|
||
à chaque cycle : toute charge relay-router démarrant relais ouverts avec
|
||
minOffS > 0 ne pouvait jamais s'enclencher, donc jamais commuter, donc jamais
|
||
valider son horodatage.
|
||
* L'armement du verrou devient PARESSEUX et TRANSITOIRE : posé au premier temps
|
||
de cycle reçu par applyAction(), il expire après sa durée configurée.
|
||
L'invariant « temps = paramètre, jamais l'horloge » est préservé.
|
||
* Test : testEcsColdStartLockExpires — palier 0 au départ, minOffS = 120,
|
||
budget 5000 W ; éteinte pendant le verrou, enclenchée après. Suite 15/15.
|
||
|
||
-- Patrick Schurig <etm.schurig@gmail.com> Sun, 09 Aug 2026 10:00:00 +0200
|
||
|
||
powersync-energy-plugin-nymea (1.15.2+etm3) trixie; urgency=medium
|
||
|
||
* Étape 1 (specs/spec_ecs.md) — trois corrections de comportement sur les
|
||
charges pilotées à relais :
|
||
* ECS-306 : le budget décrémenté par le scheduler suit le palier RÉELLEMENT
|
||
applicable. La fenêtre de verrou est de nouveau exposée à l'arbitre, en watts
|
||
(lockMinPowerW/lockMaxPowerW) — aucun index de palier ne franchit la
|
||
frontière. Sans ce canal, les charges de priorité suivante recevaient un
|
||
résidu surestimé pendant toute la fenêtre minOn, donc soutirage réseau.
|
||
* ECS-411 : le palier courant est déduit de l'état réel des Things relais au
|
||
démarrage, au lieu de repartir de 0 — le moteur ne croit plus 0 W pendant que
|
||
le ballon chauffe.
|
||
* ECS-412 : rebuild incrémental des adaptateurs. Un SetLoadConfig qui ne change
|
||
qu'un rang ne détruit plus l'adaptateur, donc ne réarme plus les verrous : sur
|
||
un ballon thermodynamique à minOn de 300-600 s, réordonner ses priorités
|
||
depuis l'app pouvait faire court-cycler le compresseur. Au démarrage à froid,
|
||
le verrou est ARMÉ (défaut sûr), jamais purgé.
|
||
* Tests : testEcsBudgetUnderLock, testEcsRestartRecovery,
|
||
testEcsRebuildPreservesLock. Suite simulation 14/14.
|
||
|
||
-- Patrick Schurig <etm.schurig@gmail.com> Sat, 08 Aug 2026 14:40:00 +0200
|
||
|
||
powersync-energy-plugin-nymea (1.15.2+etm2) trixie; urgency=medium
|
||
|
||
* Charge pilotée rév. 3 — frontière optimiseur (watt-pur) ↔ routeur (watts→relais, experience-plugin).
|
||
* RelayRouter : combinatoire watts→relais ; paliers DÉRIVÉS des relais (things "power"), verrous
|
||
minOn/minOff internes, off-before-on, agrégation currentPowerW (mesuré sinon nominal commandé).
|
||
* Optimiseur watt-pur : suppression du kind Stage ; buildSetpointAction (arrondi powerLevels / résidu).
|
||
* LoadConfig : forme relay-router (relays[]=[{thingId,powerW}] + minOnS/minOffS), validation
|
||
conditionnelle ; Get/SetLoadConfig (schéma SET "o:" pour les deux formes exclusives).
|
||
* Repli L2 unifié : compteur muet → Setpoint(0) force=true sur tous les ILoadAdapter (RelayRouter
|
||
coupe TOUS les relais). EtmVariableLoadAdapter (continu/triac) conservé.
|
||
* Tests : combinatoire/off-before-on/déduplication, repli L2 relais, chaîne config→routeur, RPC.
|
||
|
||
-- Patrick Schurig <etm.schurig@gmail.com> Sun, 28 Jun 2026 13:38:36 +0200
|
||
|
||
powersync-energy-plugin-nymea (1.15.2+etm1) trixie; urgency=medium
|
||
|
||
* Build contre nymea 1.15.2. EcsRelayAdapter + SgReadyAdapter dans init() (§1.d banc hems).
|
||
|
||
-- Patrick Schurig <etm.schurig@gmail.com> Thu, 25 Jun 2026 15:30:00 +0200
|
||
|
||
powersync-energy-plugin-nymea (1.15.0+etm1) trixie; urgency=medium
|
||
|
||
* Renommage paquet powersync-* + version 1.15.0 alignée sur libnymea-energy cible hems
|
||
* EnergyArbitrator : waterfall ECS+PAC, RuleBasedScheduler, watchdog L2
|
||
* EcsRelayAdapter : 8 paliers binaires, off-before-on, verrous minOn/minOff
|
||
* SgReadyAdapter : 4 états SG-Ready, hystérésis kForceMargin=1.2, zone morte
|
||
* §1.d banc hems : enregistrement EcsRelayAdapter+SgReadyAdapter dans init()
|
||
* Build deb : tests exclus (CONFIG+=build_tests requis séparément)
|
||
|
||
-- Patrick Schurig / ETM PowerSync <etm.schurig@gmail.com> Wed, 25 Jun 2026 12:00:00 +0200
|
||
|
||
nymea-energy-plugin-nymea (1.13.0+etm1) jammy; urgency=medium
|
||
|
||
* Fork ETM PowerSync de nymea-energy-plugin-nymea
|
||
|
||
-- Patrick Schurig / ETM PowerSync <etm.schurig@gmail.com> Sun, 07 Jun 2026 21:38:25 +0200
|
||
|
||
nymea-energy-plugin-nymea (1.13.0) jammy; urgency=medium
|
||
|
||
[ Simon Stürz ]
|
||
* Update license text and add SPDX identifier
|
||
|
||
-- jenkins <developer@nymea.io> Tue, 02 Dec 2025 10:40:27 +0100
|
||
|
||
nymea-energy-plugin-nymea (1.12.4) jammy; urgency=medium
|
||
|
||
|
||
-- jenkins <developer@nymea.io> Thu, 27 Nov 2025 11:21:05 +0100
|
||
|
||
nymea-energy-plugin-nymea (1.12.3) jammy; urgency=medium
|
||
|
||
|
||
-- jenkins <developer@nymea.io> Tue, 04 Nov 2025 11:39:56 +0100
|
||
|
||
nymea-energy-plugin-nymea (1.12.2) jammy; urgency=medium
|
||
|
||
[ Simon Stürz ]
|
||
* debian: Fix debian packaging for Qt6 packages
|
||
|
||
-- jenkins <developer@nymea.io> Wed, 29 Oct 2025 12:29:18 +0100
|
||
|
||
nymea-energy-plugin-nymea (1.12.1) jammy; urgency=medium
|
||
|
||
|
||
-- jenkins <developer@nymea.io> Mon, 13 Oct 2025 15:29:39 +0200
|
||
|
||
nymea-energy-plugin-nymea (1.12.0) jammy; urgency=medium
|
||
|
||
[ Simon Stürz ]
|
||
* Add Qt6 support
|
||
* Fix phase switching available logic
|
||
|
||
-- jenkins <developer@nymea.io> Thu, 11 Sep 2025 14:27:01 +0200
|
||
|
||
nymea-energy-plugin-nymea (1.11.1) jammy; urgency=medium
|
||
|
||
[ Simon Stürz ]
|
||
* Stop charging if the battery is under the consideration point
|
||
* Fix wrong warning regarding configuration ENV loading
|
||
|
||
-- jenkins <developer@nymea.io> Thu, 22 May 2025 15:29:54 +0200
|
||
|
||
nymea-energy-plugin-nymea (1.11.0) jammy; urgency=medium
|
||
|
||
[ Martin Lukas ]
|
||
* Add support for houshold battery level consideration
|
||
|
||
[ Simon Stürz ]
|
||
* Update permissions in order to allow a non admin system user
|
||
* Improve overload protection
|
||
* Improve phase switching logic
|
||
* Fix invalid max phase count calculation on single phase car with
|
||
phase switching charger
|
||
|
||
-- jenkins <developer@nymea.io> Thu, 20 Mar 2025 14:17:07 +0100
|
||
|
||
nymea-energy-plugin-nymea (1.10.0) jammy; urgency=medium
|
||
|
||
[ Simon Stürz ]
|
||
* Add spot market charing
|
||
|
||
-- jenkins <developer@nymea.io> Tue, 26 Nov 2024 20:53:11 +0100
|
||
|
||
nymea-energy-plugin-nymea (1.9.5) jammy; urgency=medium
|
||
|
||
|
||
-- jenkins <developer@nymea.io> Mon, 21 Oct 2024 21:05:52 +0200
|
||
|
||
nymea-energy-plugin-nymea (1.9.4) jammy; urgency=medium
|
||
|
||
|
||
-- jenkins <developer@nymea.io> Wed, 14 Aug 2024 18:36:34 +0200
|
||
|
||
nymea-energy-plugin-nymea (1.9.3) jammy; urgency=medium
|
||
|
||
[ Simon Stürz ]
|
||
* Update debian packaging and compat level
|
||
|
||
-- jenkins <developer@nymea.io> Fri, 19 Jul 2024 21:07:54 +0200
|
||
|
||
nymea-energy-plugin-nymea (1.9.2) jammy; urgency=medium
|
||
|
||
|
||
-- jenkins <developer@nymea.io> Thu, 11 Apr 2024 15:47:40 +0200
|
||
|
||
nymea-energy-plugin-nymea (1.9.1) jammy; urgency=medium
|
||
|
||
|
||
-- jenkins <developer@nymea.io> Thu, 04 Jan 2024 19:14:46 +0100
|
||
|
||
nymea-energy-plugin-nymea (1.9.0) jammy; urgency=medium
|
||
|
||
|
||
-- jenkins <developer@nymea.io> Thu, 23 Nov 2023 09:55:12 +0100
|
||
|
||
nymea-energy-plugin-nymea (1.8.4) jammy; urgency=medium
|
||
|
||
[ Michael Zanetti ]
|
||
* Don't use acquisitiontolerance to determine wheter to switcht to 3
|
||
phases
|
||
* Fix minimum car amps not being regarded in ECO mode
|
||
|
||
-- jenkins <developer@nymea.io> Tue, 14 Nov 2023 09:12:06 +0100
|
||
|
||
nymea-energy-plugin-nymea (1.8.2) jammy; urgency=medium
|
||
|
||
[ Michael Zanetti ]
|
||
* Fix effective phase count after switching phases
|
||
|
||
-- jenkins <developer@nymea.io> Wed, 28 Jun 2023 09:49:36 +0200
|
||
|
||
nymea-energy-plugin-nymea (1.8.1) jammy; urgency=medium
|
||
|
||
[ Michael Zanetti ]
|
||
* Include car minimum amps in calculation for enoughFor3PhaseCharging
|
||
|
||
-- jenkins <developer@nymea.io> Tue, 13 Jun 2023 10:20:45 +0200
|
||
|
||
nymea-energy-plugin-nymea (1.8.0) jammy; urgency=medium
|
||
|
||
[ Michael Zanetti ]
|
||
* Add support for dynamically setting the phase count
|
||
* Use currentPower instead of current for calculating the allowance
|
||
* Don't drain batteries during daytime if solar power fluctuates
|
||
* Also regard minimum charging amps for the assigned car
|
||
|
||
-- jenkins <developer@nymea.io> Tue, 16 May 2023 16:53:01 +0200
|
||
|
||
nymea-energy-plugin-nymea (1.7.0) jammy; urgency=medium
|
||
|
||
[ Simon Stürz ]
|
||
* Make sure effective phase count will be evaluated in any case for
|
||
the charger
|
||
* Fix time calculation for immediately charging required
|
||
* Fix running tests in dpkg
|
||
|
||
[ Michael Zanetti ]
|
||
* Add a lockOnUnplug setting
|
||
|
||
-- jenkins <developer@nymea.io> Thu, 09 Mar 2023 15:13:24 +0100
|
||
|
||
nymea-energy-plugin-nymea (1.6.3) jammy; urgency=medium
|
||
|
||
|
||
-- jenkins <developer@nymea.io> Wed, 01 Feb 2023 12:19:29 +0100
|
||
|
||
nymea-energy-plugin-nymea (1.6.2) jammy; urgency=medium
|
||
|
||
|
||
-- jenkins <developer@nymea.io> Mon, 16 Jan 2023 14:37:17 +0100
|
||
|
||
nymea-energy-plugin-nymea (1.6.1) jammy; urgency=medium
|
||
|
||
[ Michael Zanetti ]
|
||
* Allow ECO mode without time restriction
|
||
* No notification for normal charging
|
||
|
||
-- jenkins <developer@nymea.io> Fri, 23 Dec 2022 11:33:56 +0100
|
||
|
||
nymea-energy-plugin-nymea (1.6.0) jammy; urgency=medium
|
||
|
||
[ Simon Stürz ]
|
||
* Introduce tests and simulations
|
||
|
||
[ Michael Zanetti ]
|
||
* Allow ECO mode without time restriction
|
||
* No notification for normal charging
|
||
|
||
-- jenkins <developer@nymea.io> Sat, 10 Dec 2022 14:56:05 +0100
|
||
|
||
nymea-energy-plugin-nymea (1.5.1) jammy; urgency=medium
|
||
|
||
|
||
-- jenkins <developer@nymea.io> Mon, 07 Nov 2022 12:49:43 +0100
|
||
|
||
nymea-energy-plugin-nymea (1.5.0) jammy; urgency=medium
|
||
|
||
[ Michael Zanetti ]
|
||
* Fix current power calculation for evchargers without meter
|
||
|
||
-- jenkins <developer@nymea.io> Mon, 17 Oct 2022 14:06:35 +0200
|
||
|
||
nymea-energy-plugin-nymea (1.4.1) jammy; urgency=medium
|
||
|
||
[ Michael Zanetti ]
|
||
* Fix a crash when the root meter is removed
|
||
* Fixes for the automatic SoC calculation
|
||
* Fix acquisition tolerance
|
||
|
||
-- jenkins <developer@nymea.io> Thu, 29 Sep 2022 20:11:39 +0200
|
||
|
||
nymea-energy-plugin-nymea (1.4.0) jammy; urgency=medium
|
||
|
||
[ Michael Zanetti ]
|
||
* Add deep linking data to plugged in push notification
|
||
* Add acquisitionTolerance setting
|
||
|
||
-- jenkins <developer@nymea.io> Mon, 22 Aug 2022 17:00:54 +0200
|
||
|
||
nymea-energy-plugin-nymea (1.3.1) jammy; urgency=medium
|
||
|
||
[ Michael Zanetti ]
|
||
* Fix SoC calculation
|
||
|
||
-- jenkins <developer@nymea.io> Mon, 11 Jul 2022 15:36:45 +0200
|
||
|
||
nymea-energy-plugin-nymea (1.3.0) jammy; urgency=medium
|
||
|
||
[ Michael Zanetti ]
|
||
* Count up manual SoC while charging
|
||
* Add translations
|
||
|
||
[ Simon Stürz ]
|
||
* Update business logic behavior
|
||
|
||
-- jenkins <developer@nymea.io> Fri, 01 Jul 2022 22:50:18 +0200
|
||
|
||
nymea-energy-plugin-nymea (1.2.0) jammy; urgency=medium
|
||
|
||
|
||
-- jenkins <developer@nymea.io> Tue, 07 Jun 2022 19:45:44 +0200
|
||
|
||
nymea-energy-plugin-nymea (1.1.0) jammy; urgency=medium
|
||
|
||
[ Michael Zanetti ]
|
||
* Add CharingState information to API
|
||
* Fix calculation of remaining charging time if SoC is higher than
|
||
TSoC
|
||
* Some fixes in the smart charging logic
|
||
|
||
-- jenkins <developer@nymea.io> Tue, 10 May 2022 12:47:01 +0200
|
||
|
||
nymea-energy-plugin-nymea (1.0.0) xenial; urgency=medium
|
||
|
||
[ Michael Zanetti ]
|
||
* Improve calculation for charging power
|
||
|
||
[ Jenkins ]
|
||
|
||
-- Jenkins <jenkins@nymea.io> Thu, 10 Mar 2022 17:55:05 +0100
|
||
|
||
nymea-energy-plugin-nymea (0.1) xenial; urgency=medium
|
||
|
||
[ Michael Zanetti ]
|
||
* Import code from nymea-experience-plugin-energy
|
||
|
||
-- Jenkins <jenkins@nymea.io> Wed, 15 Dec 2021 11:54:55 +0100
|