Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015F7G5VeaPVSMeVNjiGj36p
2261 lines
160 KiB
Plaintext
2261 lines
160 KiB
Plaintext
powersync-energy-plugin-nymea (1.15.2+etm63) trixie; urgency=medium
|
||
|
||
* UN RUN EST IMMUABLE. Des valeurs neuves sous une identité de run connue
|
||
n'écrasent plus rien : écriture refusée, premières valeurs conservées, et
|
||
l'anomalie journalisée avec le partage passé/futur des créneaux modifiés —
|
||
le seul discriminant entre « le producteur fusionne l'observé » et « le run
|
||
n'identifie pas son contenu ». Constaté sur .75 le 2026-09-09 : deux
|
||
écritures pour le run 00Z à une heure d'écart, la seconde ayant effacé la
|
||
première en silence.
|
||
* La prévision est une propriété du SITE : GetForecastSite / SetForecastSite,
|
||
désignation globale sur le modèle d'Energy.SetRootMeter. Tous les plans du
|
||
site sont SOMMÉS — la courbe est superposée à la production mesurée, qui est
|
||
le total. On ne somme que des plans du MÊME run.
|
||
* AUCUNE adoption d'office : la désignation se persiste, et le jour où un
|
||
second site apparaît elle deviendrait un choix que personne n'a fait.
|
||
MISSING_FORECAST_SITE le dit une fois, avec le geste — et seulement si un
|
||
site existe.
|
||
* Une clé de run inconnue (issuedAt == 0, repli best_match) est REFUSÉE sous
|
||
son propre motif, et l'heure de réception ne s'y substitue jamais.
|
||
|
||
-- Patrick Schurig <etm.schurig@gmail.com> Wed, 09 Sep 2026 07:24:15 +0200
|
||
|
||
powersync-energy-plugin-nymea (1.15.2+etm62) trixie; urgency=medium
|
||
|
||
* Prévision PV : la table bitemporelle, la lecture du canal openmeteo, et le
|
||
RPC GetPvForecast. Une révision n'existe que si la PRÉVISION a changé ; un
|
||
run qui reconduit les mêmes valeurs met à jour lastConfirmedAt sur les
|
||
lignes existantes. Deux instants, deux noms — firstIssuedAt identifie la
|
||
prévision et ne bouge jamais, lastConfirmedAt dit jusqu'à quand elle a tenu.
|
||
lastConfirmedAt n'est JAMAIS une clé de révision, et il n'est jamais absent :
|
||
sur une ligne jamais reconduite il vaut firstIssuedAt, parce que le run
|
||
émetteur a bien affirmé ces valeurs.
|
||
* La charge utile de transport est REFUSÉE plutôt qu'interprétée au mieux :
|
||
version, unité et forme sont vérifiées, et un créneau illisible reste un
|
||
TROU. L'indexation est LUE dans la charge utile (convention), pas supposée.
|
||
* GetPvForecast est un Get* SANS *Changed : l'invariant 11 vise l'état vivant,
|
||
pas l'historique. Base séparée de energylogs.sqlite — la clé y est un
|
||
instant de mesure, la nôtre est une paire.
|
||
* La prévision n'entre PAS dans l'arbitrage. Ce lot est un affichage.
|
||
|
||
-- Patrick Schurig <etm.schurig@gmail.com> Wed, 09 Sep 2026 05:58:11 +0200
|
||
|
||
powersync-energy-plugin-nymea (1.15.2+etm61) trixie; urgency=medium
|
||
|
||
* LM-1500-h — une BORNE n'est pas écrêtée deux fois. La tête de passe éco
|
||
appliquait margeRestante, qui est la marge d'UNE phase, au plafond TRIPHASÉ
|
||
d'une borne : 6 210 W au lieu de 13 800, soit 9 A par phase au lieu de 27 —
|
||
4 kW au lieu des 11 que la voiture pouvait prendre. Le plafond de cycle
|
||
d'une borne porte DÉJÀ l'autorisation de soutirage, multipliée par le nombre
|
||
de phases occupées (evPhaseAllowanceW). La passe confort ne fait pas cette
|
||
faute et son commentaire dit pourquoi depuis 3g-2. Erreur du côté prudent,
|
||
donc invisible du disjoncteur, du délestage et de tout test de dépassement.
|
||
* LM-1500-g — écrire un MODE pendant une dérogation la termine (cause
|
||
modeChanged), et SANS restituer le mode mémorisé, qui écraserait celui que
|
||
l'utilisateur vient de choisir. Constaté au banc : le bouton « boost » de
|
||
l'app écrit encore ChargingModeNormal ; reçu pendant une dérogation, il
|
||
faisait restituer les valeurs manuelles (chargingEnabled à faux) et armait
|
||
un verrou de 300 s pendant que la dérogation commandait 9 A — deux
|
||
commandeurs pour un organe, six minutes de charge nulle servie sur le papier.
|
||
* Les motifs DEROGATION_ACTIVE et DEROGATION_ENDED sont rendus en français au
|
||
journal ; ils sortaient en « motif inconnu ».
|
||
* poseDerogation() prend l'instant en paramètre : elle lisait l'horloge murale
|
||
quand le cycle lit l'horloge injectée, et la fin par durée n'arrivait jamais
|
||
sous test.
|
||
|
||
-- Patrick Schurig <etm.schurig@gmail.com> Tue, 08 Sep 2026 11:35:34 +0200
|
||
|
||
powersync-energy-plugin-nymea (1.15.2+etm60) trixie; urgency=medium
|
||
|
||
* Dérogation : deux effets de setChargingInfo(), qui déclenche lui-même un
|
||
update(). À la pose, l'écriture du mode vient EN DERNIER — le cycle qu'elle
|
||
déclenche trouve alors la dérogation déjà posée et la sert immédiatement, au
|
||
lieu de la manquer d'un cycle. À la fin, la restitution du mode est DIFFÉRÉE
|
||
d'un tour de boucle : les fins sont évaluées depuis update(), et y écrire le
|
||
mode imbriquait un update() dans un update() — deux plans et deux dispatchs
|
||
pour le même instant, sans qu'aucun verrou de ré-entrance le signale.
|
||
|
||
-- Patrick Schurig <etm.schurig@gmail.com> Tue, 08 Sep 2026 10:52:03 +0200
|
||
|
||
powersync-energy-plugin-nymea (1.15.2+etm59) trixie; urgency=medium
|
||
|
||
* Le BOOST, implémenté comme une DÉROGATION et non comme un mode. Elle se
|
||
superpose au mode courant, sert la charge à son maximum en tête de passe
|
||
éco — devant toute autre charge, quel que soit son rang — financée au
|
||
réseau, et s'efface d'elle-même. Deux verbes : SetLoadDerogation et
|
||
ClearLoadDerogation ; la durée est OBLIGATOIRE et bornée [60 s, 6 h]
|
||
(LM-1500 : une dérogation qui ne sait pas dire quand elle finit ne devrait
|
||
pas exister). Quatre fins par mécanisme — cible atteinte, mesure perdue,
|
||
débranchement, durée — plus « cancelled », publiées dans derogationEnded[]
|
||
au niveau de la trame : au débranchement, la charge n'est déjà plus dans
|
||
loads[]. Le mode précédent est MÉMORISÉ et restitué (LM-1500-d) ; une borne
|
||
en manuel est basculée en éco pour la durée, seul cas où le mode est écrit.
|
||
DRAW_CAP la borne et le délestage la reprend : elle prend la tête de la
|
||
passe éco, elle ne surplombe pas la sûreté (LM-1500-e).
|
||
* La fin par MESURE ne mord que s'il y a une cible (LM-1500-b), et son critère
|
||
est telemetry.sessionMeasurable et non progress.regime : `progress` est
|
||
absent d'une charge sans obligation en énergie — exactement le cas du boost,
|
||
où le critère aurait été inerte.
|
||
* §15 du modèle de charge : LM-1500 et ses six déclinaisons.
|
||
Simulation::testDerogationTakesTheHeadButNotTheSafety prouve la tête de
|
||
passe par la RARETÉ — 7 500 W d'autorisation pour 8 000 demandés, la borne
|
||
de rang 9 servie entièrement et l'ECS de rang 1 réduite au reste.
|
||
|
||
-- Patrick Schurig <etm.schurig@gmail.com> Tue, 08 Sep 2026 10:29:40 +0200
|
||
|
||
powersync-energy-plugin-nymea (1.15.2+etm58) trixie; urgency=medium
|
||
|
||
* LM-1004 corrigée : le recrédit anti-clignotement appartient à la passe qui
|
||
DÉPENSE DU SURPLUS, pas à la « première passe où la charge apparaît ». La
|
||
passe éco est bornée par l'autorisation de soutirage et ne touche jamais
|
||
remainingSurplusW : lui attribuer le recrédit revenait à ne le créditer nulle
|
||
part.
|
||
* Conséquence levée : une charge servie en éco récupère de nouveau sa propre
|
||
consommation. « Minimum + surplus » sert enfin les deux — mesuré, la borne se
|
||
privait de 751 W de surplus qu'elle mangeait elle-même.
|
||
|
||
-- ETM Schurig <dev@etm-schurig.eu> Mon, 07 Sep 2026 13:10:00 +0200
|
||
|
||
powersync-energy-plugin-nymea (1.15.2+etm57) trixie; urgency=medium
|
||
|
||
* « Surplus + minimum réseau » (LM-1013-b-vi) : le mode
|
||
ChargingModeEcoWithMinCurrent sert enfin un minimum. La passe éco rend
|
||
max(cadence de l'obligation, minimum), et le minimum ne se tient jamais —
|
||
quand l'obligation est tenue, la consigne retombe sur lui, pas sur zéro.
|
||
* Pour une borne, la valeur se DÉRIVE de maxChargingCurrent.minValue ×
|
||
phaseCount × 230 V à chaque cycle. Jamais stockée : une copie écrite à
|
||
l'appairage se périmerait au premier recâblage.
|
||
* Le motif ECO_FLOOR_GRID porte minAlwaysW et boundBy, et OMET les grandeurs
|
||
d'obligation sur une charge à minimum seul — un remainingS à 0 inventerait
|
||
une échéance imminente.
|
||
* Contrat : les trois descriptions de ChargingMode corrigées. Normal est
|
||
MANUEL, pas un boost — le moteur ne commande rien, seule la protection de
|
||
surcharge la touche encore.
|
||
* Les quatre instants stockés sont ancrés en UTC à la capture. Un QDateTime
|
||
local résout son époque à la conversion : measuredSince et command.since
|
||
dérivaient d'une heure au changement de fuseau.
|
||
|
||
-- ETM Schurig <dev@etm-schurig.eu> Mon, 07 Sep 2026 08:30:00 +0200
|
||
|
||
powersync-energy-plugin-nymea (1.15.2+etm56) trixie; urgency=medium
|
||
|
||
* Divergence commande/mesure JOURNALISÉE à l'ouverture et à la fermeture
|
||
(qCInfo). Elle n'était que publiée dans la télémétrie et tenue dans un QHash
|
||
en mémoire : rien ne comptait les épisodes, le hash était purgé dès que
|
||
l'écart cessait, et chaque redémarrage effaçait l'état. Le silence du journal
|
||
était garanti par construction.
|
||
* GetEnergyRatios / EnergyRatiosChanged publient trois champs de plus :
|
||
periodStartedAt (epoch du minuit local), measuredSince (epoch de pose de la
|
||
baseline) et timeZone (IANA). Toujours présents, même quand les deux taux
|
||
sont omis.
|
||
* periodStartedAt ≠ measuredSince : la baseline se repose à chaque redémarrage
|
||
de nymead, donc en plein jour. Sans la paire, l'écran écrit « aujourd'hui »
|
||
sur des chiffres qui ne parlent que de l'après-midi.
|
||
* Le schéma des ratios est désormais déclaré UNE fois (energyRatiosSchema),
|
||
servi au Get et au Changed — invariant 11.
|
||
|
||
-- ETM Schurig <dev@etm-schurig.eu> Sat, 05 Sep 2026 09:30:00 +0200
|
||
|
||
powersync-energy-plugin-nymea (1.15.2+etm55) trixie; urgency=medium
|
||
|
||
* §10 / LM-1013-b-v : sous le régime `unmeasurable`, la passe éco commande à
|
||
TAUX CONSTANT — cible ÷ jour — au lieu de l'étalement `reste / temps restant`.
|
||
Sous ce régime le numérateur est figé à la cible par contrat : un rapport dont
|
||
seul le dénominateur décroît ne suit plus un retard, il suit l'horloge.
|
||
* `atMaximum` n'est plus jamais vrai sous ce régime : le drapeau annonce que
|
||
l'étalement a cessé, et sans étalement il n'a rien à annoncer.
|
||
* Chiffré : obligation 4 kWh, plafond 7 kW, fenêtre d'un jour — l'ancienne loi
|
||
achetait 19 010 Wh, soit ×4,75 l'obligation, le facteur suivant ln(T·P/E)+1.
|
||
* Le régime mesuré est INCHANGÉ : durcissement et point de non-retour intacts,
|
||
et une contre-épreuve du test le vérifie au même instant et sur le même
|
||
numérateur.
|
||
|
||
-- ETM Schurig <dev@etm-schurig.eu> Fri, 04 Sep 2026 17:15:00 +0200
|
||
|
||
powersync-energy-plugin-nymea (1.15.2+etm54) trixie; urgency=medium
|
||
|
||
* +etm53 DÉPLOYÉ sur .75 le 2026-09-04, version relue et `nymead` actif : `bindingPhase` et le
|
||
correctif amont de la tension sont sur la box.
|
||
* MOCK — LE COMPTEUR INTÈGRE ENFIN. `totalEnergyConsumed` n'était écrit par personne : il
|
||
valait sa valeur par défaut indéfiniment. Un compteur figé est un compteur qui N'EXISTE PAS
|
||
pour tout ce qui l'intègre — `deliveredWh` restait à 0, `remainingWh` à 4 000, et l'étalement
|
||
adaptatif n'était JAMAIS exercé. Le comportement se confondait avec `unmeasurable` pour une
|
||
raison entièrement différente et bénigne. QUATRIÈME occurrence d'un mock plus homogène que la
|
||
réalité.
|
||
* EN EXPORT IL NE RECULE PAS, il ne bouge pas : un compteur de consommation est monotone, et
|
||
le faire reculer produirait un cas que le terrain ne produit pas — celui que la garde de
|
||
sémantique traite comme une anomalie. Le mock fabriquerait le défaut que le moteur détecte.
|
||
* LM-1014-b APPLIQUÉE AU MOCK, question posée et répondue : `totalEnergyConsumed` est déclaré
|
||
`cached`, donc il CONTINUE au redémarrage. Repartir de zéro ferait reculer un cumul monotone.
|
||
* CONTRAT — `purchasedWh` gagne un SECOND USAGE, sous `measured` cette fois, et il n'est pas
|
||
redondant. Mesuré : il accumule (0 → 9,15 → 18,33 → 27,52 Wh) pendant que `deliveredWh` peut
|
||
rester PLAT. Cette divergence est un contrôle que l'écran n'aurait pas su fabriquer : de
|
||
l'acheté qui monte avec du livré plat signifie soit que la charge ne consomme pas ce qu'on
|
||
paie, soit que le compteur ne compte pas. Écrit parce que le jour où les deux coïncideront,
|
||
quelqu'un jugera le champ redondant — or c'est précisément quand ils DIVERGENT que la paire
|
||
devient informative, et un champ qui ne sert que dans l'anomalie n'a pas l'air utile le reste
|
||
du temps.
|
||
* Le test du cumul a une borne basse LARGE, et c'est délibéré : il ne contrôle ni la cadence du
|
||
minuteur ni l'alignement du premier tic, qui n'intègre rien. Une borne serrée en ferait un
|
||
test instable — c'est-à-dire un test qu'on relancerait jusqu'à ce qu'il passe.
|
||
* Suite : simulation 66/66, loadmodel 22/22, charging 48/48 identique à la référence,
|
||
spotmarket 32/32, doxygen 0 avertissement.
|
||
|
||
-- Patrick Schurig <etm.schurig@gmail.com> Fri, 04 Sep 2026 14:00:00 +0200
|
||
|
||
powersync-energy-plugin-nymea (1.15.2+etm53) trixie; urgency=medium
|
||
|
||
* [upstream-fix] rootmeter — LA TENSION EST DÉSORMAIS LOCALE À CHAQUE PHASE. `voltage` était
|
||
déclarée une seule fois hors des trois blocs et mutée par l'un d'eux : une phase sans tension
|
||
déclarée était calculée avec celle d'une autre.
|
||
* ET LE DÉFAUT N'ÉTAIT PAS CELUI QU'ON CROYAIT. L'interface `energymeter` rend `currentPhaseA`
|
||
et `voltagePhaseA` OBLIGATOIRES, B et C optionnels : la branche « puissance ÷ tension » de A
|
||
est MORTE PAR CONSTRUCTION. La fuite n'allait donc pas de A vers B, comme la note d'origine
|
||
l'affirmait, mais de B vers C. Plus étroit, et bien réel.
|
||
* DÉMONTRÉ : B à 200 V, C à 4 000 W sans tension déclarée → 5,0 A de marge annoncés au lieu de
|
||
7,6. Deux conditions, dont aucune n'était évidente avant d'écrire le test : le masque doit
|
||
couvrir B ET C (interroger C seule n'exécute pas le bloc de B), et C doit être la phase la
|
||
plus contrainte, sinon le `qMin` retient B et masque l'écart.
|
||
* MOCK — classe `meterPowerOnly` : puissances par phase et tension de B, SANS les courants de
|
||
B/C ni la tension de C. Sans elle le défaut restait inatteignable. TROISIÈME fois qu'ouvrir
|
||
le mock révèle un défaut plutôt que de seulement débloquer un test, après `dryRelay` et après
|
||
la correction du raisonnement sur l'écrêtage de la marge.
|
||
* `draw.bindingPhase` — LAQUELLE des phases borne. `perPhaseBound` disait qu'une phase borne,
|
||
jamais laquelle : l'écran ne pouvait dire que « bridé » sans dire par quoi, et enverrait
|
||
mesurer au hasard sur trois phases dont deux vont bien. Le motif DRAW_CAP la porte aussi,
|
||
mais il n'est émis que si une charge est REFUSÉE — un cycle qui borde sans rien refuser
|
||
laisserait l'écran muet. Les deux endroits ne se recouvrent pas.
|
||
* CONTRAT — une installation qui EXPORTE peut être bridée, et l'écran ne doit jamais présenter
|
||
une marge globale ni la meilleure phase : c'est la PIRE qui décide. Contre-intuitif au point
|
||
qu'un installateur cherchera l'erreur ailleurs.
|
||
* AGENTS.md — une conclusion JUSTE posée sur un raisonnement FAUX survit jusqu'à ce qu'on
|
||
bâtisse dessus, parce que la conclusion PROTÈGE le raisonnement de l'examen : un résultat
|
||
faux se voit, un résultat juste ferme la question. Deux occurrences de deux côtés de la
|
||
frontière. Parade : quand une décision est écrite avec sa raison, la raison mérite son propre
|
||
contrôle — un test qui vérifie la conclusion ne vérifie pas le raisonnement, et c'est le
|
||
raisonnement que le lot suivant utilisera.
|
||
* Suite : simulation 65/65, loadmodel 22/22, charging 48/48 identique à la référence,
|
||
spotmarket 32/32, doxygen 0 avertissement.
|
||
|
||
-- Patrick Schurig <etm.schurig@gmail.com> Fri, 04 Sep 2026 11:00:00 +0200
|
||
|
||
powersync-energy-plugin-nymea (1.15.2+etm52) trixie; urgency=medium
|
||
|
||
* MOCK — tension et courant PAR PHASE réglables indépendamment des puissances. Le mock posait
|
||
`currentPhaseX = currentPowerPhaseX / 230` : les deux familles d'états étaient cohérentes PAR
|
||
CONSTRUCTION, si bien qu'aucun scénario ne pouvait produire une triphasée déséquilibrée ni
|
||
une tension ≠ 230 V. Paramètres OPTIONNELS : sans eux, le comportement d'origine est conservé
|
||
et aucun scénario existant ne bouge.
|
||
* LM-1210-a ENFIN EXERCÉE — le choix de la pire phase faute de masque était écrit, raisonné, et
|
||
jamais vérifié. Phase B saturée à 30 A sous un plafond de 25 pendant que A et C exportent, et
|
||
la maison exporte GLOBALEMENT : la marge retenue est celle de B, négative, et non celle du
|
||
total qui rassurerait à tort. Ce manque était nommé DEUX FOIS par deux chemins indépendants —
|
||
l'inventaire du mock et le balayage des contrôles — avant d'être comblé.
|
||
* ET L'OUVERTURE A CORRIGÉ UNE ERREUR DE RAISONNEMENT, pas seulement comblé un trou. Le premier
|
||
test attendait qu'une phase en export donne une marge SUPÉRIEURE au plafond — c'est ce que le
|
||
design affirmait. Il a échoué : `calculateAllowanceAmpere()` part du plafond et ne fait que
|
||
des `qMin`, donc la marge y est ÉCRÊTÉE. Physiquement juste, mais la phrase était fausse : la
|
||
conclusion tenait, la raison non. Corrigée dans la spec et dans le code.
|
||
* C'est la DEUXIÈME fois qu'ajouter de la diversité au mock trouve un défaut plutôt que de
|
||
seulement débloquer un test — après `dryRelay`, qui avait révélé le déréférencement du
|
||
contrôleur. La diversité du mock n'est pas un confort de test.
|
||
* Note écrite à côté de la garde du compteur : l'app dépend de `connected` pour REFUSER
|
||
D'AFFICHER, jamais pour COMPTER — et elle a raison. Si `connected` ment, l'écran dit « hors
|
||
ligne » à tort, rien n'est mal compté. Une comptabilité n'a pas droit à cette tolérance :
|
||
même donnée non fiable, deux usages, deux verdicts. S'abstenir sur un doute est sûr,
|
||
accumuler sur un doute ne l'est pas.
|
||
* Suite : simulation 64/64, loadmodel 22/22, charging 48/48 identique à la référence,
|
||
spotmarket 32/32, doxygen 0 avertissement.
|
||
|
||
-- Patrick Schurig <etm.schurig@gmail.com> Thu, 03 Sep 2026 17:00:00 +0200
|
||
|
||
powersync-energy-plugin-nymea (1.15.2+etm51) trixie; urgency=medium
|
||
|
||
* URGENT — CONTRADICTION SUPPRIMÉE entre `progressMeasurable` et `regime`. Le premier dit ce
|
||
que le MATÉRIEL permet, le second ce que le MOTEUR fait ; seul l'EvAdapter remplissait
|
||
`sessionMeasurable`, donc sur toute charge non-VE le premier disait oui et le second non.
|
||
L'avertissement de saisie de l'app se déclenchant sur `progressMeasurable == false`, il
|
||
restait MUET exactement sur les charges achetées à l'aveugle. `sessionWh` est désormais
|
||
alimenté depuis le compteur DÉSIGNÉ pour les charges non-VE.
|
||
* LM-1012-d — UN ACCUMULATEUR NE PARIE JAMAIS SUR CE QU'UN PLUGIN FAIT DE SES ÉTATS EN TOMBANT.
|
||
Avertissement de l'app intégré AVANT l'écriture, et il était décisif : un compteur qui
|
||
disparaît peut publier 0 au lieu de se taire. Mesuré sur la V2C — `currentPower` 4 396 → 0 et
|
||
les booléens à false, mais `sessionEnergy` resté à sa dernière valeur. La politique dépend du
|
||
plugin, ce qui la rend inutilisable comme garde.
|
||
* ET IL N'EXISTE AUCUN SIGNAL DE JOIGNABILITÉ FIABLE — vérifié : `setupComplete` parle du setup
|
||
et non du lien, `connected` est maintenu par le plugin (V2C après trois échecs, un compteur
|
||
générique peut ne jamais basculer). La garde ne pouvait donc pas reposer là-dessus.
|
||
* LA GARDE PORTE SUR LA SÉMANTIQUE DU COMPTEUR, seule chose que le moteur sache sans parier :
|
||
un recul sur compteur de SESSION est une session neuve (recapture) ; sur compteur de VIE,
|
||
c'est une anomalie (GEL, jamais de recapture). Sans elle : base reprise à 0 pendant
|
||
l'absence, retour du compteur avec 10 kWh réels, `deliveredWh` saute à 10 000 en un cycle et
|
||
l'obligation se déclare TENUE sans que rien n'ait été livré. R6 ne l'attrape pas — elle
|
||
n'interdit ECO_FLOOR_MET que sous `unmeasurable`, et ce chemin-là est MESURÉ. Contre-épreuve :
|
||
garde retirée, le régime reste « measured » pendant la panne.
|
||
* `tools/audit-controles.py` — écrit en tête qu'il NE TRANCHE PAS et ne doit jamais trancher :
|
||
au balayage, neuf des dix identités relevées contrôlaient réellement. Un outil qui aurait
|
||
rendu un verdict aurait supprimé neuf contrôles utiles.
|
||
* AGENTS.md — la diversité du mock n'est pas un confort de test, c'est une BORNE SUPÉRIEURE sur
|
||
ce que la suite peut prouver, et elle se fixe en écrivant les classes, pas les tests.
|
||
Troisième occurrence documentée.
|
||
* Quatrième occurrence du piège d'insertion doxygen, rattrapée par le même contrôle — comme
|
||
annoncé.
|
||
* Suite : simulation 63/63, loadmodel 22/22, charging 48/48 identique à la référence,
|
||
spotmarket 32/32, doxygen 0 avertissement.
|
||
|
||
-- Patrick Schurig <etm.schurig@gmail.com> Thu, 03 Sep 2026 15:00:00 +0200
|
||
|
||
powersync-energy-plugin-nymea (1.15.2+etm50) trixie; urgency=medium
|
||
|
||
* BALAYAGE — « qu'est-ce que ce contrôle aurait attrapé s'il avait échoué ? » passée à toute la
|
||
suite. Ce n'est pas une liste de tests à supprimer, c'est la liste de ce qu'on croyait
|
||
vérifié et qui ne l'était pas (docs/BALAYAGE_CONTROLES_20260903.md).
|
||
* ASSERTIONS CONDITIONNELLES DANS UNE BOUCLE : 0. La seule qui existait a été corrigée le
|
||
2026-09-01 ; l'outil le confirme et le surveillera.
|
||
* IDENTITÉS AUTO-RÉFÉRENTIELLES : 10 relevées, UNE seule vaine. Le motif signale, la lecture
|
||
tranche. `authorisedW − committedW == remainingW` relisait une affectation d'UNE LIGNE :
|
||
retirée, avec sa raison écrite à sa place — et la distinction d'avec l'identité du BUDGET,
|
||
qui croise `remainingSurplusW` filé le long de la cascade avec `allocatedW` accumulé en TROIS
|
||
sites, et qui mérite donc d'être assertée. Supprimer sans dire pourquoi aurait invité
|
||
quelqu'un à la remettre.
|
||
* LA MOITIÉ QUI COMPTE — l'index annonçait 47 règles « spécifiées seules ». Il mesurait la
|
||
CITATION, pas la couverture : DOUZE étaient couvertes par un test qui ne les nommait pas.
|
||
Citées par un test : 22 → 40. Spécifiées seules : 47 → 31. Les liens sont désormais
|
||
vérifiables par la machine au lieu de reposer sur la mémoire de qui a écrit quoi.
|
||
* Sur les 31 restantes, 23 appartiennent à des familles NON IMPLANTÉES (sonde thermique,
|
||
transport §14, optimiseur externe) : écrire leur test poserait un invariant sur du code que
|
||
rien n'émet. Six sont du cadrage mal classé. Et UNE SEULE manque vraiment — LM-1210-a, la
|
||
pire phase faute de masque, qu'aucun test n'exerce PARCE QUE le mock ne sait pas produire une
|
||
triphasée déséquilibrée. Même angle mort que l'inventaire, nommé deux fois désormais.
|
||
* `tools/audit-controles.py` pérennise les deux motifs. Il ne remplace pas la question, il la
|
||
pose là où elle se pose souvent.
|
||
* Le résultat le plus utile n'est pas le défaut trouvé, c'est le CHIFFRE CORRIGÉ : on croyait
|
||
47 règles non couvertes, il y en avait 31, dont une seule qui manque vraiment.
|
||
* Suite : simulation 62/62, loadmodel 22/22, charging 48/48 identique à la référence,
|
||
spotmarket 32/32, doxygen 0 avertissement.
|
||
|
||
-- Patrick Schurig <etm.schurig@gmail.com> Thu, 03 Sep 2026 10:00:00 +0200
|
||
|
||
powersync-energy-plugin-nymea (1.15.2+etm49) trixie; urgency=medium
|
||
|
||
* CORRECTIF — sur `measured → unmeasurable → measured` DANS LA MÊME PÉRIODE, `deliveredWh`
|
||
repartait de zéro. La base était retirée puis reprise à la valeur courante de `sessionWh`,
|
||
donc l'énergie livrée pendant les tronçons mesurés antérieurs DISPARAISSAIT. Deux
|
||
conséquences, et la seconde coûte : l'écran sous-estime, et la passe éco RACHÈTE ce qui a
|
||
déjà été livré.
|
||
* PAS THÉORIQUE : c'est le motif d'une V2C dont le lien Modbus bat — chaque perte et retour
|
||
fait basculer le régime deux fois, et chaque aller-retour effaçait ce qui précède.
|
||
Contre-épreuve : tronçon jeté, l'avancement retombe de 1500 Wh à 0.
|
||
* On accumule désormais par PÉRIODE, pas par tronçon. Et la clôture se fait sur la DERNIÈRE
|
||
valeur lue tant que la mesure existait, jamais sur celle du cycle où le régime tombe : à cet
|
||
instant le Thing ne publie plus, et s'en servir fabriquerait une énergie.
|
||
* TRANCHÉ — le cumul de période repart de zéro à la bascule de période, et les trous avec lui.
|
||
* TRANCHÉ — `measuredSince` garde le PREMIER instant mesuré, et l'intermittence se dit à part
|
||
(`measuredGaps`, publié seulement s'il y en a). Redater à chaque retour effacerait l'histoire
|
||
au lieu de la raconter ; ne rien dire ferait croire à une continuité que le battement dément.
|
||
L'écran peut écrire « mesuré PAR INTERMITTENCE depuis 09:14 », qui est la phrase vraie.
|
||
* LM-1014-c — un cumul se teste aussi de part et d'autre d'un CHANGEMENT DE RÉGIME, pas
|
||
seulement de période. LM-1014-b disait quoi faire à la bascule de période et ne disait rien
|
||
de la bascule de régime à l'intérieur d'une période : c'est là que le défaut vivait.
|
||
* Le test a échoué deux fois avant d'être juste, et la seconde était le test : mon horodatage
|
||
« après échéance » visait la MÊME échéance que le premier, donc rien ne basculait.
|
||
* Suite : simulation 62/62, loadmodel 22/22, charging 48/48 identique à la référence,
|
||
spotmarket 32/32, doxygen 0 avertissement.
|
||
|
||
-- Patrick Schurig <etm.schurig@gmail.com> Wed, 02 Sep 2026 11:00:00 +0200
|
||
|
||
powersync-energy-plugin-nymea (1.15.2+etm48) trixie; urgency=medium
|
||
|
||
* MOCK — classe `dryRelay` : un relais SEC, qui commute et ne mesure pas. Elle ouvre le cas que
|
||
le banc ne savait pas produire : une charge SAINE et NON MESURÉE. Jusqu'ici
|
||
`measurement.source: none` n'était atteignable qu'avec un Thing manquant, ce qui traîne
|
||
`available: false` et mélange deux affirmations — « rien ne mesure » et « c'est en panne ».
|
||
Un relais sec est banal sur le terrain, et parfaitement sain.
|
||
* `testHealthyButUnmeasuredDiffersFromFaulty` vérifie ce que RIEN ne pouvait vérifier avant :
|
||
deux charges, même `source: none`, `available` opposés, et un `faultCode` sur la seule qui
|
||
est en défaut. Ce test n'aurait pas pu s'écrire hier.
|
||
* ET L'AJOUT A RÉVÉLÉ QUE LE MOCK AVAIT SA PROPRE PRÉMISSE FAUSSE : `executeAction()`
|
||
déréférençait le contrôleur HTTP de CHAQUE Thing sans vérifier, supposant que toute classe en
|
||
possède un. Un relais sec n'en a pas besoin. Le plantage fut immédiat et net ; l'hypothèse,
|
||
elle, était invisible tant qu'aucune classe ne la contredisait — l'homogénéité du mock la
|
||
protégeait, exactement comme elle masquait les cas qu'il ne savait pas produire.
|
||
* AGENTS.md — la règle générale sur les identités : une identité dont les DEUX TERMES viennent
|
||
de la même source ne vérifie rien. Elle est vraie par construction, et elle rassure là où
|
||
rien ne protège. La question n'est donc pas « tient-elle ? » mais « de quoi parle-t-elle
|
||
vraiment ? ». Et c'est le MÊME défaut que la contre-épreuve qui ne mord pas, vu de l'autre
|
||
côté : dans les deux cas le voyant est vert, dans les deux cas il n'a rien mesuré. Réflexe
|
||
commun : demander ce que ce contrôle aurait attrapé s'il avait échoué.
|
||
* Suite : simulation 62/62, loadmodel 22/22, charging 48/48 identique à la référence,
|
||
spotmarket 32/32, doxygen 0 avertissement.
|
||
|
||
-- Patrick Schurig <etm.schurig@gmail.com> Wed, 02 Sep 2026 09:00:00 +0200
|
||
|
||
powersync-energy-plugin-nymea (1.15.2+etm47) trixie; urgency=medium
|
||
|
||
* R6 — LA MESURABILITÉ DE L'AVANCEMENT SE DÉCLARE DANS `GetLoadConfig`, en `r:`, et non dans
|
||
la télémétrie. `measurement` n'existe que pour les charges arbitrées ; or une charge qu'on
|
||
DÉCLARE est celle qui n'y est pas encore. L'écran serait muet au moment exact où il doit
|
||
parler — celui de la saisie, avant qu'une obligation n'achète à l'aveugle tous les jours.
|
||
* TROIS CAUSES, TROIS GESTES : `noMeter` (réparable en une manipulation), `meterWithoutEnergy`
|
||
(compteur à remplacer), `noSessionEnergy` (rien à faire, c'est le matériel). Même structure
|
||
que les trois sources de DRAW_CAP.
|
||
* ET `meterThingId` NE PERMET PAS DE LE DÉDUIRE : un compteur peut publier `currentPower` sans
|
||
`totalEnergyConsumed` — mesuré sur l'ECS du banc, et reproduit au test. La puissance ne
|
||
suffit pas, il faut une énergie. Contre-épreuve : déduction par `meterThingId` seul, le cas
|
||
du banc passe pour mesurable.
|
||
* INVENTAIRE À FROID DU MOCK (docs/INVENTAIRE_MOCK.md). Trois fois en trois jours qu'un cas
|
||
réel s'est révélé inatteignable par la suite ; ce n'est plus un incident, c'est une propriété
|
||
du banc d'essai — il est PLUS HOMOGÈNE QUE LA RÉALITÉ. La table des classes dit ce qui est
|
||
discriminant et ce qui ne l'est pas, et quatre cas restent structurellement introuvables,
|
||
dont `source: none` pour une charge SAINE (toutes les classes pilotables portent une
|
||
puissance) et la branche `temperature`, écrite et jamais exécutée.
|
||
* La règle qui en sort : avant d'écrire un test qui exerce une ABSENCE, vérifier que le mock
|
||
peut la produire — une assertion sur un cas inatteignable ne passe pas, elle NE S'EXÉCUTE
|
||
PAS, ce qui est pire, car elle compte comme verte. Symptôme reconnaissable : la
|
||
contre-épreuve ne mord pas. Parade : compter les cas examinés et échouer si le compte est nul.
|
||
* AGENTS.md — l'asymétrie des latences notée comme une PROPRIÉTÉ et non une chance : le refus
|
||
qui coûte de l'argent est celui de la descente, et c'est le plus vite constaté. Ce n'est vrai
|
||
que parce que le seuil vient du mécanisme ; une constante unique perdrait la propriété.
|
||
* Et la question opératoire appliquée aux trois autres identités du contrat. Résultat inégal,
|
||
et c'est le but : Σ counts == targetW est vérifiée hors de ses termes ; l'identité de `draw`
|
||
repose sur le fait que le compteur voie toute la maison, ce que RIEN ne vérifie ;
|
||
Σ levels[].targetW == estimatedPowerW est INTERNE AU PLAN et ne dit rien du matériel — il a
|
||
fallu `command.divergent`, hors de la formule, pour relier les deux.
|
||
* Suite : simulation 61/61, loadmodel 22/22, charging 48/48 identique à la référence,
|
||
spotmarket 32/32, doxygen 0 avertissement.
|
||
|
||
-- Patrick Schurig <etm.schurig@gmail.com> Tue, 01 Sep 2026 14:00:00 +0200
|
||
|
||
powersync-energy-plugin-nymea (1.15.2+etm46) trixie; urgency=medium
|
||
|
||
* LE RECRÉDIT EST CONDITIONNÉ À L'OBÉISSANCE CONSTATÉE. Il rendait au budget la conso d'une
|
||
charge sur l'hypothèse qu'on pourra la lui reprendre ; contre un refus établi, les watts
|
||
prêtés étaient réalloués aux suivantes pendant que la charge continuait de les tirer.
|
||
Mesuré : 4 000 W alloués pour 500 W de surplus réel. Suspension faite, B ne reçoit plus que
|
||
les 500 W qui existent.
|
||
* LE SEUIL VIENT DU MÉCANISME, jamais d'une constante — `ILoadAdapter::commandSettleS()`. Un
|
||
écart RÉCENT est une latence : une charge qui vient d'être commandée n'a pas atteint sa
|
||
consigne, et suspendre là ferait osciller ce qui va bien. Latences MESURÉES sur la V2C :
|
||
montée ~30 s, descente ~7 s. L'asymétrie joue dans le bon sens — le refus qui coûte de
|
||
l'argent est celui de la descente, et c'est le plus vite constaté. Défaut prudent à 60 s :
|
||
l'oscillation d'une charge saine est un coût certain, le refus n'est qu'un risque.
|
||
* ET ÇA SE DIT : motif `RECREDIT_SUSPENDED` avec `refusedSinceS` et `withheldW`. Une
|
||
suspension change l'allocation de TOUTES les charges suivantes ; sans le motif, on
|
||
chercherait un défaut de cascade là où une charge n'obéit pas.
|
||
* AGENTS.md — UNE CINQUIÈME CLASSE, et la seule qu'aucun contrôle ne pouvait attraper : une
|
||
PRÉMISSE FAUSSE SOUS UNE ARITHMÉTIQUE EXACTE. Les quatre autres se laissent prendre par un
|
||
instrument ; celle-ci n'a aucune trace — `surplus + recrédit − alloué == restant` tient
|
||
exactement, que la charge obéisse ou non. La parade : une identité qui tient ne prouve pas
|
||
que le modèle est juste, seulement qu'il est cohérent. Ce qu'une identité ne peut jamais
|
||
attraper, c'est une hypothèse sur le monde. D'où la question à poser à chaque identité
|
||
publiée : sur quoi repose-t-elle qui ne soit pas dans ses propres termes ?
|
||
* Troisième occurrence du même piège d'insertion doxygen — un bloc glissé entre un
|
||
commentaire existant et sa fonction. L'outil l'attrape à chaque fois, et c'est exactement
|
||
ce pour quoi il existe.
|
||
* Suite : simulation 60/60, loadmodel 22/22, charging 48/48 identique à la référence,
|
||
spotmarket 32/32, doxygen 0 avertissement. Contre-épreuve : suspension débranchée, le
|
||
recrédit revient à 4 400 W.
|
||
|
||
-- Patrick Schurig <etm.schurig@gmail.com> Tue, 01 Sep 2026 12:00:00 +0200
|
||
|
||
powersync-energy-plugin-nymea (1.15.2+etm45) trixie; urgency=medium
|
||
|
||
* Commande perdue, étape 2 — L'ÉCART COMMANDÉ/MESURÉ EST PUBLIÉ (`command`), et gouverné par
|
||
`measurement.source`. Absent sous `none` : il n'y a rien à comparer, et `divergent: false`
|
||
affirmerait une concordance qu'on ne peut pas constater.
|
||
* L'ASYMÉTRIE VA DANS LES DEUX SENS, et le contrat la dit maintenant en entier. Sous `device`,
|
||
une CONCORDANCE ne prouve rien — plusieurs bornes renvoient leur consigne en guise de
|
||
puissance — mais une DIVERGENCE dit que la charge n'obéit pas. Sous `meter`, c'est l'inverse :
|
||
la concordance est une preuve, la divergence ouvre trois hypothèses.
|
||
* Le test le DÉMONTRE au lieu de l'énoncer : la borne du mock dérive sa puissance de sa propre
|
||
consigne, donc expectedW == observedW PAR CONSTRUCTION. La divergence a dû être exercée par
|
||
un compteur explicite — seul régime où elle a valeur de preuve.
|
||
* UN ÉCART N'EST PAS UN DÉFAUT, et le champ ne le qualifie pas. Véhicule qui limite, câble en
|
||
16 A, mode manuel : le même nombre qu'une commande perdue. La cause n'est pas dans les watts,
|
||
et un écran qui en ferait une alerte accuserait des installations saines.
|
||
* `since` est ce qui rend le champ utile : sans lui, une divergence PERMANENTE ne se distingue
|
||
pas d'un déphasage d'un cycle. C'est toute la différence entre le normal et le cas relevé.
|
||
* `divergent` est publié plutôt que laissé au client : la tolérance est une connaissance du
|
||
moteur. La laisser au client ferait réinventer un seuil par écran.
|
||
* AGENTS.md — une contre-épreuve qui NE MORD PAS ne prouve pas que le code est bon, elle
|
||
prouve qu'on a perturbé au mauvais endroit. Appliqué à moi-même dans le même lot : mon
|
||
assertion « sans mesure, pas de `command` » était VACANTE — aucune charge du test n'avait
|
||
`source: none`, la boucle ne s'exécutait jamais, et la garde retirée le test passait
|
||
toujours. Toutes les classes du mock portent un état de puissance ; il a fallu une charge au
|
||
CÂBLAGE MANQUANT pour produire le cas. La contre-épreuve mord désormais.
|
||
* Suite : simulation 59/59, loadmodel 22/22, charging 48/48 identique à la référence,
|
||
spotmarket 32/32, doxygen 0 avertissement.
|
||
|
||
-- Patrick Schurig <etm.schurig@gmail.com> Tue, 01 Sep 2026 10:30:00 +0200
|
||
|
||
powersync-energy-plugin-nymea (1.15.2+etm44) trixie; urgency=medium
|
||
|
||
* CONTRAT — `remainingWh` sous `unmeasurable` vaut LA CIBLE INTACTE : `remainingWh ==
|
||
targetWh`. 2 000 Wh restants sur une cible de 2 000 Wh signifie « rien de mesurable », PAS
|
||
« rien livré ». Les deux lectures commandent des phrases d'écran opposées, et l'écran ne peut
|
||
trancher que si le contrat le dit. Écrit dans INTERFACE.md.
|
||
* Rien n'est soustrait parce que soustraire l'intégration interne ferait franchir la frontière
|
||
à une ESTIMATION sous l'allure d'un fait — le chemin de `carBatteryLevel`, refusé en
|
||
LM-1009 D3. Que `regime` voyage dans les mêmes params ne suffirait pas : un client lisant
|
||
`remainingWh` sans regarder `regime` obtiendrait un nombre calculé sans rien pour le lui dire.
|
||
* IDENTITÉ ÉPINGLÉE sous `measured` : progress.deliveredWh + params.remainingWh ==
|
||
params.targetWh. Troisième contrôle vérifiable, après Σ counts == targetW et
|
||
authorisedW − committedW == remainingW. Elle ne tient QUE sous `measured` — sous
|
||
`unmeasurable`, c'est `remainingWh == targetWh` qui prend le relais, et c'est le régime qui
|
||
dit laquelle appliquer.
|
||
* HONNÊTETÉ SUR LA PORTÉE DE CE TEST : la contre-épreuve naïve (soustraire `deliveredWh` sous
|
||
unmeasurable) NE MORD PAS, puisque `deliveredWh` y vaut 0. L'assertion est donc un
|
||
COROLLAIRE de `delivered == false`, pas une garde indépendante. La garde réelle est R6, et
|
||
elle a été vérifiée mordante au bon niveau : publier l'estimation comme un fait fait sortir
|
||
ECO_FLOOR_MET sous unmeasurable.
|
||
* Commande perdue, étape 1 — MÉMOIRE de la commande dans EvAdapter. Les adaptateurs en watts
|
||
la gardaient déjà (l'écrêtage de verrou l'exige) ; la borne, pilotée par le proxy amont,
|
||
n'avait jamais eu à se relire. C'est exactement là que le relevé du 2026-08-31 porte : sans
|
||
ce souvenir, l'écart commandé/constaté n'est pas CALCULABLE. Publication seule, comportement
|
||
constant. La commande ÉCRÊTÉE est retenue, jamais la demande — comparer la demande ferait
|
||
voir un écart là où l'adaptateur a fait son travail.
|
||
* Suite : simulation 58/58, loadmodel 22/22, charging 48/48 identique à la référence,
|
||
spotmarket 32/32, doxygen 0 avertissement.
|
||
|
||
-- Patrick Schurig <etm.schurig@gmail.com> Tue, 01 Sep 2026 08:30:00 +0200
|
||
|
||
powersync-energy-plugin-nymea (1.15.2+etm43) trixie; urgency=medium
|
||
|
||
* DÉPLOYÉ SUR .75 le 2026-09-01, version relue sur la box et `nymead` actif. Les quatre motifs
|
||
du §10 sont présents dans le binaire posé — vérifié sur machine, pas déduit du script.
|
||
* Piège de vérification, noté : `authorisedW`, `perPhaseBound` et `binding` sont des
|
||
QStringLiteral, donc stockés en UTF-16 et INVISIBLES à `strings` par défaut. Un premier
|
||
relevé les donnait absents. `strings -el` les trouve. Vérifier une chaîne dans un binaire
|
||
demande de savoir comment elle y est encodée.
|
||
|
||
* ECO_FLOOR_MISSED — le motif a désormais son ÉMETTEUR, et c'est pourquoi il est créé
|
||
maintenant. Ce matin il n'en avait pas : le créer aurait posé un invariant sur un code que
|
||
rien ne produit, un test toujours vert. La bascule de période (LM-1013-c) l'a fait naître —
|
||
une obligation abandonnée est un fait que le moteur produit, et un fait produit se dit.
|
||
* TROIS CAUSES, TROIS GESTES : `drawCap` envoie regarder l'abonnement, `loadCapacity` la fiche
|
||
technique de la charge, `unmeasurable` ce que la borne publie. `unavailable` s'y ajoute
|
||
(ECS-410). Sans la cause, l'écran n'affiche qu'un reproche sans recours.
|
||
* `missingWh` ABSENT sous `unmeasurable`, jamais 0 : on ne sait pas ce qui a été livré, donc
|
||
pas davantage ce qui manquait. Publier un chiffre inventerait la grandeur même dont
|
||
l'absence EST le problème. Mesuré : « non tenue sur 2000 Wh — l'avancement n'était pas
|
||
mesurable, et le manque n'est pas chiffrable ».
|
||
* R5 VÉRIFIÉE contre la maquette plutôt que supposée conforme : `progress { regime, targetWh,
|
||
deliveredWh absent si unmeasurable }` correspond exactement.
|
||
* UN ÉCART ASSUMÉ, signalé plutôt qu'aligné en silence : la maquette donnait
|
||
ECO_FLOOR_GRID { deliveredWh, targetWh, gridW } ; le moteur publie `remainingWh` au lieu de
|
||
`deliveredWh`. Raison : `deliveredWh` vit déjà dans `progress`, et le republier ferait deux
|
||
sources pour un même fait (LM-1012). Surtout, `remainingWh` n'est PAS dérivable sous
|
||
`unmeasurable` — où `deliveredWh` est absent — et c'est justement le cas où l'on achète.
|
||
* R8 — LE POINT DE RUPTURE SE LIT SUR `DRAW_CAP`, JAMAIS SUR UN SOLDE À ZÉRO. `remainingW` à 0
|
||
arrive aussi quand tout a été alloué normalement : un écran qui le guetterait signalerait une
|
||
rupture inexistante et manquerait la vraie. DRAW_CAP n'est émis que lorsqu'un plafond a
|
||
réellement refusé, et il porte lequel. Écrit dans INTERFACE.md.
|
||
* Suite : simulation 58/58, loadmodel 22/22, charging 48/48 identique à la référence,
|
||
spotmarket 32/32, doxygen 0 avertissement.
|
||
|
||
-- Patrick Schurig <etm.schurig@gmail.com> Mon, 31 Aug 2026 11:00:00 +0200
|
||
|
||
powersync-energy-plugin-nymea (1.15.2+etm42) trixie; urgency=medium
|
||
|
||
* §10 étape D (2/2) — LA PASSE ÉCO EXISTE. Deux passes sur la MÊME liste, MÊME ordre ; ce qui
|
||
change est la BORNE — l'autorisation de soutirage pour l'éco, le reliquat de surplus pour le
|
||
confort. L'ordre lexicographique sur (niveau, rang) en découle : le rang classe les CHARGES,
|
||
la hiérarchie éco/confort classe les BESOINS et surplombe le rang.
|
||
* UNE SEULE COMMANDE PAR CHARGE ET PAR CYCLE (LM-1003). Le niveau éco ne commande pas : il est
|
||
fusionné dans l'action de la passe confort. Émettre deux fois ferait commuter un relais en
|
||
passe 1 puis le recommuter en passe 2 — du court-cycling fabriqué par l'arbitre lui-même.
|
||
* ECO_FLOOR_GRID REND LISIBLE LA TRAJECTOIRE, PAS L'INSTANT. La cadence ne monte pas, elle fait
|
||
un mur : 333 W à six heures de l'échéance, le plafond dès seize minutes. Un client qui lit
|
||
« 1000 W achetés » ne peut pas deviner ce qu'il paiera dans deux heures. Le motif porte donc
|
||
gridW, remainingWh, targetWh, remainingS, regime et atMaximum — combien, pour quelle
|
||
obligation, à quelle échéance, sur quoi on achète, et si l'étalement a cessé.
|
||
* R6 ARMÉE À DEUX ENDROITS : `computeEcoTarget()` ne conclut jamais « tenue » sous un régime
|
||
non mesuré, et la passe refuse d'émettre ECO_FLOOR_MET dans ce cas. Un invariant qui ne tient
|
||
qu'à un seul endroit tient mal.
|
||
* L'AUTORISATION DE SOUTIRAGE BORNE L'ÉCO : le plancher éco est une raison d'acheter, pas un
|
||
droit de faire disjoncter. Contre-épreuve : bornage retiré, 1000 W au lieu de 400.
|
||
* Recrédit attribué à la PREMIÈRE passe où la charge apparaît, par champ explicite (LM-1004) :
|
||
la discipline ne se teste pas, le champ si. Le recréditer deux fois rendrait au budget de
|
||
surplus une puissance que l'éco a financée au réseau.
|
||
* MESURÉ AVANT D'ÊTRE ASSERTÉ, encore : deux niveaux, Σ targetW == estimatedPowerW,
|
||
counts ventilés entre budget.allocatedW et draw.committedW. Aucun scénario existant ne
|
||
portait d'obligation, donc aucune référence — les valeurs ont été relevées d'abord.
|
||
* INVARIANCE PRÉSERVÉE LÀ OÙ ELLE A ENCORE UN SENS : aucune charge existante ne déclare
|
||
d'obligation, donc la passe éco ne produit rien pour elles et charging reste IDENTIQUE à la
|
||
référence ligne à ligne. C'est le garde-fou qui reste utile dans une étape qui, par ailleurs,
|
||
change le comportement.
|
||
* Suite : simulation 57/57, loadmodel 22/22, charging 48/48 identique, spotmarket 32/32.
|
||
|
||
-- Patrick Schurig <etm.schurig@gmail.com> Mon, 31 Aug 2026 10:05:00 +0200
|
||
|
||
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
|