La consigne de puissance reseau appartient au controleur
Fermer le contacteur ne suffit pas : au-dessus du SOC minimal, l ESS en autoconsommation ne preleve que sa consigne et laisse la batterie porter maison et voiture. Nuit du 01/09 : charge HC demarree a 01:25, arretee a 01:26 par le garde batterie, contacteur ferme et inerte jusqu a 02:00. La consigne a 4200 W du proprietaire n etait pas decorative : c est elle qui faisait fonctionner la prise de relais nocturne depuis le 07/08. La retirer le 31/08 a supprime la substitution de journee et casse la nuit. Le controleur ecrit desormais la consigne lui-meme, a partir du besoin mesure, seulement pendant une session reseau deliberee, avec une bande asymetrique : trop basse se corrige tout de suite, trop haute ne coute rien. Deux ecritures en 25 minutes au lieu d une toutes les 30 s. Golden 51 -> 56. Verifie en reel : batterie a 0 A, voiture a 3500 W. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KKzkkVTG6KXnGXFFC7X9XJ
This commit is contained in:
@@ -124,6 +124,17 @@ pushAction(
|
||||
context.sourceMode === 'grid_hc_allowed' || context.sourceMode === 'grid_forced'
|
||||
);
|
||||
|
||||
// =====================================================
|
||||
// CONSIGNE DE PUISSANCE RESEAU
|
||||
// Fermer le contacteur ne suffit pas : sans consigne, l'ESS laisse la
|
||||
// batterie porter la maison et la voiture (nuit du 01/09/2026).
|
||||
// =====================================================
|
||||
pushAction(
|
||||
'grid_power_setpoint',
|
||||
calls.grid_power_setpoint,
|
||||
!!calls.grid_power_setpoint?.execute
|
||||
);
|
||||
|
||||
// =====================================================
|
||||
// AUTO START OFF SI BESOIN
|
||||
// =====================================================
|
||||
|
||||
@@ -1,3 +1,10 @@
|
||||
// Bornes de la consigne de puissance reseau. Le maximum est celui declare par
|
||||
// l'entite elle-meme (number.airtria_mpii_v1_ac_power_setpoint_l1, max 10000 W),
|
||||
// pas un chiffre choisi ici. La bande morte evite de reecrire la meme valeur
|
||||
// toutes les 30 s sans empecher la correction d'une derive externe.
|
||||
const GRID_SETPOINT_MAX_W = 10000;
|
||||
const GRID_SETPOINT_DEADBAND_W = 100;
|
||||
|
||||
function round1(v) {
|
||||
return Math.round(v * 10) / 10;
|
||||
}
|
||||
@@ -309,6 +316,67 @@ reason.push(`SOC=${round1(parsed.soc)}%`);
|
||||
reason.push(`Batt=${round1(parsed.batteryCurrentA)}A`);
|
||||
reason.push(`PV=${Math.round(parsed.pvPowerW)}W`);
|
||||
reason.push(`PV10=${Math.round(parsed.pvPowerAvg10mW)}W`);
|
||||
// =====================================================
|
||||
// CONSIGNE DE PUISSANCE RESEAU (01/09/2026)
|
||||
// =====================================================
|
||||
// Fermer le contacteur ne suffit PAS. Au-dessus du SOC minimal, l'ESS en
|
||||
// autoconsommation ne preleve que `ac_power_setpoint` et laisse la batterie
|
||||
// porter tout le reste : nuit du 01/09, contacteur ferme, consigne 50 W,
|
||||
// batterie a -803 W pour 761 W de maison, voiture arretee par le garde
|
||||
// "batterie trop sollicitee". La consigne est donc le levier qui rend une
|
||||
// session reseau REELLE, et il doit appartenir au controleur, pas rester un
|
||||
// reglage manuel permanent (celui-ci provoquait la substitution du solaire
|
||||
// par le reseau en journee, cf. HANDOFF 27/28).
|
||||
//
|
||||
// La valeur n'est pas une constante : c'est le besoin mesure du moment, la
|
||||
// maison telle qu'elle consomme plus la voiture au courant que le controleur
|
||||
// s'autorise. Le plafond joue le role de marge : quand la voiture n'est pas a
|
||||
// son plafond, la consigne est deja genereuse, et l'exces ne peut pas partir
|
||||
// dans la batterie (DVCC tenue a 2 A par l'Auto Charge Manager pendant un
|
||||
// takeover). Hors session, retour a la valeur de repos.
|
||||
const gridSessionActive =
|
||||
parsed.acInAvailable &&
|
||||
(context.sourceMode === 'grid_forced' || context.sourceMode === 'grid_hc_allowed');
|
||||
|
||||
const gridSetpointNeedW = gridSessionActive
|
||||
? clamp(
|
||||
Math.round(derived.houseNonEvcsW + derived.physicalCapA * derived.acVoltageRef),
|
||||
params.gridSetpointIdleW,
|
||||
GRID_SETPOINT_MAX_W
|
||||
)
|
||||
: params.gridSetpointIdleW;
|
||||
|
||||
// La bande est ASYMETRIQUE, et c'est le point. Une consigne trop BASSE fait
|
||||
// porter la difference par la batterie : c'est le defaut de la nuit du 01/09,
|
||||
// il se corrige immediatement. Une consigne trop HAUTE ne coute rien : l'ESS
|
||||
// ne preleve que ce qu'il peut placer, et le courant de charge de la batterie
|
||||
// est tenu par ailleurs (DVCC, Auto Charge Manager). Deux leviers, deux roles
|
||||
// -- la consigne dit ce que le reseau a le DROIT de servir, DVCC dit ce que la
|
||||
// batterie a le droit de prendre. Ne pas confondre les deux.
|
||||
//
|
||||
// On ecrit donc avec une marge d'un pas de courant EVCS (grandeur du systeme,
|
||||
// pas une constante inventee) pour que le bruit de la maison ne rende pas la
|
||||
// consigne insuffisante au cycle suivant, et on ne redescend que si elle est
|
||||
// devenue franchement inutile. Sans cela la boucle 30 s reecrivait a chaque
|
||||
// cycle en poursuivant le bruit.
|
||||
const gridSetpointSlackW = gridSessionActive
|
||||
? Math.max(GRID_SETPOINT_DEADBAND_W, Math.round(params.currentStepUpA * derived.acVoltageRef))
|
||||
: 0;
|
||||
|
||||
const gridSetpointTargetW = clamp(
|
||||
gridSetpointNeedW + gridSetpointSlackW,
|
||||
0,
|
||||
GRID_SETPOINT_MAX_W
|
||||
);
|
||||
|
||||
const gridSetpointCurrentW = Number.isFinite(parsed.gridSetpointW)
|
||||
? parsed.gridSetpointW
|
||||
: null;
|
||||
const gridSetpointNeedsWrite =
|
||||
gridSetpointCurrentW === null ||
|
||||
gridSetpointCurrentW < gridSetpointNeedW ||
|
||||
gridSetpointCurrentW > gridSetpointNeedW + 2 * gridSetpointSlackW + GRID_SETPOINT_DEADBAND_W;
|
||||
|
||||
reason.push(`Grid=${parsed.gridAvailable ? 'on' : 'off'} (${Math.round(parsed.gridPowerW)}W)`);
|
||||
// Trace de la fenetre d'etablissement : sans elle, une lecture du logbook
|
||||
// pendant le transitoire donne l'impression que le systeme decide n'importe
|
||||
@@ -316,6 +384,9 @@ reason.push(`Grid=${parsed.gridAvailable ? 'on' : 'off'} (${Math.round(parsed.gr
|
||||
if (derived.gridSettling) {
|
||||
reason.push(`ETABLISSEMENT reseau ${Math.round(parsed.gridTransitionAgeSec)}s/${params.gridSettleSeconds}s (mesures PV/batterie non fiables)`);
|
||||
}
|
||||
if (gridSessionActive) {
|
||||
reason.push(`Consigne reseau=${gridSetpointTargetW}W`);
|
||||
}
|
||||
reason.push(`Cap=${derived.physicalCapA}A`);
|
||||
reason.push(`Ref=${round1(derived.currentReferenceA)}A`);
|
||||
reason.push(`Target=${round1(desiredCurrentA)}A`);
|
||||
@@ -1001,6 +1072,16 @@ return [{
|
||||
}
|
||||
},
|
||||
|
||||
grid_power_setpoint: {
|
||||
execute: gridSetpointNeedsWrite,
|
||||
domain: 'number',
|
||||
service: 'set_value',
|
||||
data: {
|
||||
entity_id: 'number.gx_device_ac_power_setpoint',
|
||||
value: gridSetpointTargetW
|
||||
}
|
||||
},
|
||||
|
||||
evcs_auto_start_off: {
|
||||
execute: autoStartAction === 'turn_off',
|
||||
domain: 'switch',
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
"acin1_last_changed_ts": as_timestamp(states.sensor.victron_ac_in1_available.last_changed, 0) if states.sensor.victron_ac_in1_available is not none else 0,
|
||||
"grid_switch_last_changed_ts": as_timestamp(states.switch.edf_general_ok_switch.last_changed, 0) if states.switch.edf_general_ok_switch is not none else 0,
|
||||
"grid_settle_seconds": states("input_number.evcs_grid_settle_seconds"),
|
||||
"grid_setpoint_w": states("number.gx_device_ac_power_setpoint"),
|
||||
"grid_setpoint_idle_w": states("input_number.evcs_grid_setpoint_idle_w"),
|
||||
|
||||
"today_remaining_kwh": states("sensor.energy_production_today_remaining"),
|
||||
"tomorrow_kwh": states("sensor.energy_production_tomorrow"),
|
||||
|
||||
@@ -245,6 +245,13 @@ const parsed = {
|
||||
|
||||
gridIsOn: low(input.grid_state || 'off') === 'on',
|
||||
acInAvailable: num(input.victron_acin1_available ?? input.ac_in1_available, 0) === 1,
|
||||
|
||||
// Consigne de puissance réseau de l'ESS, lue telle qu'elle est appliquée.
|
||||
// C'est ELLE qui décide si un contacteur fermé sert à quelque chose : au-dessus
|
||||
// du SOC minimal, l'ESS en autoconsommation ne prélève que cette puissance et
|
||||
// laisse la batterie porter le reste. Nuit du 01/09 : contacteur fermé,
|
||||
// consigne 50 W, batterie à -803 W pour 761 W de maison.
|
||||
gridSetpointW: num(input.grid_setpoint_w, NaN),
|
||||
gridAvailable:
|
||||
(low(input.grid_state || 'off') === 'on') ||
|
||||
(num(input.victron_acin1_available ?? input.ac_in1_available, 0) === 1),
|
||||
@@ -419,6 +426,10 @@ const params = {
|
||||
// input_number.evcs_grid_settle_seconds ; absent => 180.
|
||||
gridSettleSeconds: clamp(num(input.grid_settle_seconds, 180), 0, 900),
|
||||
|
||||
// Consigne réseau au repos. 50 W est le défaut Victron (légèrement positif
|
||||
// pour ne jamais injecter), pas une valeur choisie ici.
|
||||
gridSetpointIdleW: clamp(num(input.grid_setpoint_idle_w, 50), 0, 500),
|
||||
|
||||
probeEnabled: low(input.probe_enabled || 'off') === 'on',
|
||||
probeMinutes: Math.max(1, num(input.probe_minutes, 3)),
|
||||
probeRetryMinutes: Math.max(5, num(input.probe_retry_minutes, 45)),
|
||||
|
||||
Reference in New Issue
Block a user