Les trois conditions, d'un bloc. INSERTION EN QUEUE. provisionEvChargerConfig() posait rang min − 1, écrasé à 1 — donc à égalité avec la charge que l'installateur avait posée là exprès. C'est désormais 1 + max(rangs). Queue de LISTE et non de domaine : les domaines ne sont pas contigus, donc « juste après le dernier ev » exigerait de pousser une charge d'un autre domaine, ce que la condition 2 interdit. Et 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. Plus fidèle au principe que la formulation d'origine. UNICITÉ. validateRanks() refuse une écriture où deux charges portent le même rang. Avec la mise en garde écrite noir sur blanc : l'égalité ne coûte pas le déterminisme — le tri est total sur (rang, identifiant) depuis 3g-2 — elle coûte la gouvernabilité. S'y tromper ferait chercher le défaut dans le tri. ET LA RÈGLE GÉNÉRALE QUI EN SORT (LM-1209-a-bis) : une vérification qui ÉCARTE et une vérification qui REFUSE ne peuvent pas être la même fonction. validateSet() nomme ce qui rend l'ensemble dangereux et load() s'en sert pour écarter ; 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, soit ECS-413 rouvert par une autre porte. Les deux sont séparées ; validateRanks() n'est appelée que par setConfigs(). Au chargement, un doublon hérité est signalé bruyamment et CONSERVÉ. o:rankOrigin. Énumération fermée "auto" | "user", absente par défaut. Une énumération et non un booléen parce que le troisième état est le point : `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 » ; un booléen ne sait pas se taire. Même motif que measurement.source, et la spec le nomme désormais comme un motif de conception plutôt qu'une décision au cas par cas. Et la précision demandée en écrivant : l'effacement porte sur le RANG, jamais sur la réception d'une écriture. SetLoadConfig remplace en bloc, donc réordonner une AUTRE charge renvoie forcément l'entrée de celle-ci — l'effacer à cette occasion perdrait une marque encore vraie. L'omission ne lève rien ; pour lever sans déplacer, un client envoie "user". Un "auto" fabriqué est refusé, l'aller-retour verbatim reste licite. Trois tests. testAutoProvisionedRankGoesToTheQueue vérifié échouant avec l'insertion en tête — et il échoue en disant que la borne n'entre PAS en configuration, les deux mécanismes se répondant : le rang libre par construction et le refus d'écriture en filet. testRankOriginFollowsTheRankNotTheWrite couvre le cas qui se serait perdu, un client naïf qui omet la clé en déplaçant une autre charge. testRankUniquenessRefusesWriteButNeverDiscards couvre la moitié qui ne se voit pas : qu'un chargement conserve ce qu'il aurait pu écarter. amd64 0 erreur / 0 avertissement. simulation 31/31, charging 16/16, loadmodel 20/20, spotmarket 7/7. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015F7G5VeaPVSMeVNjiGj36p
1185 lines
79 KiB
Plaintext
1185 lines
79 KiB
Plaintext
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
|