feat(control): connect physical feedback to bounded V4 command and fallback paths

This commit is contained in:
ENELIX Agent
2026-10-03 11:40:54 +00:00
parent 8eb9688942
commit 9878197aea
24 changed files with 891 additions and 7 deletions
+18 -2
View File
@@ -14,6 +14,11 @@ namespace Belevo\EnelixEMS {
function IPS_GetInstance($id) { return ['InstanceStatus'=>$GLOBALS['objects'][$id]->ready?102:201,
'ModuleInfo'=>['ModuleID'=>$id===44234?'{437FB683-517F-4FEC-8CCB-FE6B0A62B69E}':'{6F771B18-59D4-4C8A-B951-3B2FE9F6A2C4}']]; }
function IPS_GetProperty($id,$key) { return $GLOBALS['objects'][$id]->props[$key]; }
function IPS_VariableExists($id) { return in_array($id,[50001,50002],true); }
function IPS_GetVariable($id) { $t=$GLOBALS['feedbackAt'][$id]??$GLOBALS['clock'];return ['VariableUpdated'=>$t,'VariableChanged'=>$t]; }
function IPS_GetObject($id) { return ['ParentID'=>200,'ObjectIdent'=>'source'.$id]; }
function GetValue($id) { return $id===50001?$GLOBALS['objects'][17004]->op['gridW']:$GLOBALS['objects'][17004]->actual['b']; }
function IPS_RequestAction($id,$key,$value) {
$GLOBALS['calls'][]=[$id,$key];$b=$GLOBALS['objects'][$id];
if ($key==='NetzfahrplanV4TestStart') $b->V4BatterieTestStarten($value);
@@ -76,7 +81,10 @@ namespace Belevo\EnelixEMS {
private function berechtigungLizenziert($k) { return $this->licensed; }
private function actieveDummy() {}
private function aktiveVerbraucherIDs() { return [44234]; }
private function netzfahrplanV4LokaleVorschauwerte() { return [$this->op,$this->actual]; }
private function netzfahrplanV4LokaleVorschauwerte() {
$op=$this->op;$f=json_decode($GLOBALS['objects'][44234]->GetV4BatterieRueckmeldung(),true);
$op['localFeedback']=['b'=>$f];return [$op,$this->actual];
}
private function regeln($renew) { $this->fallback++; }
public function tick(&$allocation,$consumers=null,$sync=true) {
$this->v4ManagerTestUebergabe($allocation,$consumers??[['InstanzID'=>44234,'Daten'=>[]]],$sync);
@@ -105,7 +113,14 @@ namespace {
'maxChargeW'=>5000.,'maxDischargeW'=>5000.,'actuatorWatchdogEvidenceId'=>'synthetic-hardware-watchdog',
'accountingEvidenceId'=>'synthetic-meter-boundary-v1','acceptEstimatedPeak'=>true,'controlContext'=>$r['plan']['controlContext']];
$r['controlledTrial']=$serverAuthority?$a:null;
$m=new ManagerHarness($r,$op);$b=new BatteryHarness();$GLOBALS['objects']=[17004=>$m,44234=>$b];
$m=new ManagerHarness($r,$op);$b=new BatteryHarness();$GLOBALS['objects']=[17004=>$m,44234=>$b];$GLOBALS['feedbackAt']=[];
$fc=['version'=>1,'installationId'=>$id,'assetId'=>'b','managerId'=>17004,'batteryInstanceId'=>44234,
'mode'=>'physical_sum','allowEstimatedForTrial'=>false,'maxSkewSeconds'=>30,'maxAgeSeconds'=>60,
'idleToleranceW'=>50.0,'trackingToleranceW'=>200.0,'sources'=>[]];
foreach ([50001=>'grid',50002=>'physical'] as $vid=>$role) $fc['sources'][]=['key'=>'source'.$vid,'role'=>$role,'variableId'=>$vid,'parentId'=>200,'ident'=>'source'.$vid,'factorToW'=>1.0];
$b->props['NetzfahrplanV4RueckmeldungKonfiguration']=json_encode($fc);
$op['localFeedback']=['b'=>json_decode($b->GetV4BatterieRueckmeldung(),true)];
$m->props['NetzfahrplanV4RegeltestErlaubt']=$permissions;$b->props['NetzfahrplanV4RegeltestErlaubt']=$permissions;
$b->props['NetzfahrplanV4GeraeteWatchdogNachweis']='synthetic-hardware-watchdog';
$start=['confirmation'=>'START_BOUNDED_V4_CONTROL_TRIAL','sessionId'=>$a['sessionId'],'durationSeconds'=>300,'maxChargeW'=>5000.,'maxDischargeW'=>5000.];
@@ -157,5 +172,6 @@ namespace {
$bad=$r;$bad['controlledTrial']['validUntil']=gmdate('c',$now+120);rejects(fn()=>Gate::befehl($bad,$op,['b'=>0.],$local,$now,$now),'oversized server authorization lease');
$bad=$r;$bad['controlledTrial']['acceptEstimatedPeak']=false;rejects(fn()=>Gate::befehl($bad,$op,['b'=>0.],$local,$now,$now),'peak uncertainty needs explicit acceptance');
require __DIR__ . '/additional_checks.php';
require __DIR__ . '/feedback_checks.php';
echo "TOTAL $count checks passed. Mocked IPS, clocks and register writes only.\n";
}
+22
View File
@@ -0,0 +1,22 @@
<?php
// Run through actual Manager/Battery trial traits, fresh read helper, and simulated register driver.
[$m,$b,$s]=fixture();$b->props['NetzfahrplanV4RueckmeldungKonfiguration']='{}';
rejects(fn()=>start($m,$s),'trial cannot use old filtered feedback when mapping missing');check($b->writes===[],'missing corrected feedback sends no command');
[$m,$b,$s]=fixture();start($m,$s);tick($m);$feedbackAt[50002]=$clock-61;
$a=tick($m);check(!$m->active()&&!$b->active()&&isset($a['Sollwerte'][44234]),'physical feedback expiry returns battery to normal allocation');
check(end($b->writes)===0,'expired physical feedback revokes prior command');
[$m,$b,$s]=fixture();start($m,$s);tick($m);$feedbackAt[50002]=$clock-61;$b->watchdog();
check(!$b->active()&&end($b->writes)===0,'independent battery read catches staleness without manager tick');
[$m,$b,$s]=fixture();start($m,$s);tick($m);$c=json_decode($b->props['NetzfahrplanV4RueckmeldungKonfiguration'],true);$c['trackingToleranceW']=250;
$b->props['NetzfahrplanV4RueckmeldungKonfiguration']=json_encode($c);$b->watchdog();
check(!$b->active()&&end($b->writes)===0,'mapping revision change revokes active battery session');
[$m,$b,$s]=fixture();start($m,$s);tick($m);$c=$m->command();$c['sequence']++;$c['feedbackConfigHash']=str_repeat('f',64);
rejects(fn()=>$b->V4BatterieTestBefehl(json_encode($c)),'changed feedback command binding rejected');
[$m,$b,$s]=fixture();start($m,$s);tick($m);$c=$m->command();check(isset($c['feedbackConfigHash'],$c['feedbackSourceOldestAt'])&&array_key_exists('importLimitW',$c),'command includes source provenance and limits');
$c['sequence']++;unset($c['exportLimitW']);rejects(fn()=>$b->V4BatterieTestBefehl(json_encode($c)),'missing explicit grid limit not interpreted as unlimited');
[$m,$b,$s]=fixture();start($m,$s);tick($m);$c=$m->command();$c['sequence']++;$c['importLimitW']=1000.;$c['watts']=0;
$m->op['gridW']=1500.;$b->measurements['Ladezustand']=15.;
rejects(fn()=>$b->V4BatterieTestBefehl(json_encode($c)),'final device clipping cannot silently violate accepted grid boundary');
check(!$b->active()&&end($b->writes)===0,'unreachable final limit aborts software trial');
[$m,$b,$s]=fixture();start($m,$s);tick($m);$feedbackAt[50002]=$clock+1;tick($m);
check(!$m->active(),'future physical sample is not smoothed into control');