fix(v4): retry transient confirmed device reads
This commit is contained in:
@@ -49,7 +49,8 @@ trait BatterieNetzfahrplanV4RueckmeldungTrait
|
|||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
$reader,
|
$reader,
|
||||||
static fn():int=>time()
|
static fn():int=>time(),
|
||||||
|
static function(int $microseconds):void { usleep($microseconds); }
|
||||||
);
|
);
|
||||||
$this->SetBuffer('V4ConfirmedFeedback',json_encode($result,JSON_THROW_ON_ERROR));
|
$this->SetBuffer('V4ConfirmedFeedback',json_encode($result,JSON_THROW_ON_ERROR));
|
||||||
return $result;
|
return $result;
|
||||||
|
|||||||
@@ -47,10 +47,10 @@ trait ManagerNetzfahrplanV4EmpfangTrait
|
|||||||
/** Appended dynamically; existing form.json and parallel portal GUI stay untouched. */
|
/** Appended dynamically; existing form.json and parallel portal GUI stay untouched. */
|
||||||
private function netzfahrplanV4EmpfangFormular(): array
|
private function netzfahrplanV4EmpfangFormular(): array
|
||||||
{
|
{
|
||||||
return ['type' => 'ExpansionPanel', 'caption' => 'V4 Planempfang / Sollwert-Vorschau', 'items' => [
|
return ['type' => 'ExpansionPanel', 'caption' => 'V4 Planempfang / Prognosemanager', 'items' => [
|
||||||
['type' => 'Label', 'caption' => 'NUR BEOBACHTEN. Kein Lade-/Entladebefehl. Der alte intelligente Netzfahrplan muss AUS bleiben.'],
|
['type' => 'Label', 'caption' => 'Der alte intelligente Netzfahrplan muss AUS bleiben. Die separate Laufzeitvariable unter dem Manager schaltet den zeitlich begrenzten Testbetrieb ein oder aus.'],
|
||||||
['type' => 'CheckBox', 'name' => 'NetzfahrplanV4EmpfangAktiv', 'caption' => 'V4 Schattenplan empfangen und Sollwert anzeigen (keine Steuerung)'],
|
['type' => 'CheckBox', 'name' => 'NetzfahrplanV4EmpfangAktiv', 'caption' => 'V4 Schattenplan empfangen und Sollwert anzeigen (keine Steuerung)'],
|
||||||
['type' => 'Label', 'caption' => 'Diagnose und Sollwerte stehen in den beiden V4-Vorschauvariablen unterhalb des Managers.'],
|
['type' => 'Label', 'caption' => 'Schalter: V4 Prognosemanager aktiv (Testbetrieb). Ausschalten beendet V4 und fordert sofort 0 W an. Maximale Laufzeit ab dem ersten Einschalten: 48 Stunden.'],
|
||||||
['type' => 'Button', 'caption' => 'Plan jetzt pruefen (Abruf begrenzt auf 1/min)',
|
['type' => 'Button', 'caption' => 'Plan jetzt pruefen (Abruf begrenzt auf 1/min)',
|
||||||
'onClick' => "IPS_RequestAction(\$id, 'NetzfahrplanV4Empfangen', true);"],
|
'onClick' => "IPS_RequestAction(\$id, 'NetzfahrplanV4Empfangen', true);"],
|
||||||
]];
|
]];
|
||||||
@@ -172,6 +172,10 @@ trait ManagerNetzfahrplanV4EmpfangTrait
|
|||||||
if (is_string($configured) && !in_array(trim($configured),['','{}'],true)) {
|
if (is_string($configured) && !in_array(trim($configured),['','{}'],true)) {
|
||||||
if (count($topology)!==1) throw new RuntimeException('feedback_multi_asset_adapter_required');
|
if (count($topology)!==1) throw new RuntimeException('feedback_multi_asset_adapter_required');
|
||||||
$f=json_decode(ENELIX_GetV4BatterieRueckmeldung($controller),true,32,JSON_THROW_ON_ERROR);
|
$f=json_decode(ENELIX_GetV4BatterieRueckmeldung($controller),true,32,JSON_THROW_ON_ERROR);
|
||||||
|
if (($f['status']??null)==='unavailable') {
|
||||||
|
$reason=is_string($f['reason']??null)?$f['reason']:'unknown';
|
||||||
|
throw new RuntimeException('feedback_unavailable:'.substr($reason,0,180));
|
||||||
|
}
|
||||||
if (($f['installationId']??null)!==$this->ReadAttributeString('LizenzInstallationID')
|
if (($f['installationId']??null)!==$this->ReadAttributeString('LizenzInstallationID')
|
||||||
|| ($f['assetId']??null)!==$asset['ID'] || ($f['managerId']??null)!==$this->InstanceID
|
|| ($f['assetId']??null)!==$asset['ID'] || ($f['managerId']??null)!==$this->InstanceID
|
||||||
|| ($f['batteryInstanceId']??null)!==$controller || !is_numeric($f['batteryW']??null)
|
|| ($f['batteryInstanceId']??null)!==$controller || !is_numeric($f['batteryW']??null)
|
||||||
|
|||||||
@@ -25,7 +25,8 @@ final class NetzfahrplanV4Geraeteabruf
|
|||||||
callable $moduleId,
|
callable $moduleId,
|
||||||
callable $refresh,
|
callable $refresh,
|
||||||
callable $reader,
|
callable $reader,
|
||||||
callable $clock
|
callable $clock,
|
||||||
|
?callable $pause = null
|
||||||
): array {
|
): array {
|
||||||
$config = NetzfahrplanV4Rueckmeldung::configuration($config);
|
$config = NetzfahrplanV4Rueckmeldung::configuration($config);
|
||||||
$groups = [];
|
$groups = [];
|
||||||
@@ -43,7 +44,17 @@ final class NetzfahrplanV4Geraeteabruf
|
|||||||
if (!in_array($module, [self::MBUS_DEVICE, self::MODBUS_DEVICE, self::MODBUS_ADDRESS], true)) {
|
if (!in_array($module, [self::MBUS_DEVICE, self::MODBUS_DEVICE, self::MODBUS_ADDRESS], true)) {
|
||||||
throw new InvalidArgumentException('feedback_refresh_module_unsupported:' . $sources[0]['key']);
|
throw new InvalidArgumentException('feedback_refresh_module_unsupported:' . $sources[0]['key']);
|
||||||
}
|
}
|
||||||
if ($refresh($parentId, $module) !== true) {
|
$confirmed = false;
|
||||||
|
for ($attempt = 1; $attempt <= 4; ++$attempt) {
|
||||||
|
if ($refresh($parentId, $module) === true) {
|
||||||
|
$confirmed = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if ($attempt < 4 && $pause !== null) {
|
||||||
|
$pause(200000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!$confirmed) {
|
||||||
throw new InvalidArgumentException('feedback_device_read_failed:' . $sources[0]['key']);
|
throw new InvalidArgumentException('feedback_device_read_failed:' . $sources[0]['key']);
|
||||||
}
|
}
|
||||||
$confirmedAt = $clock();
|
$confirmedAt = $clock();
|
||||||
|
|||||||
@@ -35,6 +35,12 @@ dcheck($f['deviceReadConfirmed']===true&&$f['sourceOldestAt']===$n-1,'successful
|
|||||||
dcheck($f['sourceVariableOldestAt']===$n-100,'original VariableUpdated retained separately');
|
dcheck($f['sourceVariableOldestAt']===$n-100,'original VariableUpdated retained separately');
|
||||||
dcheck($f['batteryW']===-3000.0&&$f['usableForTrial'],'unchanged values accepted after confirmed device response');
|
dcheck($f['batteryW']===-3000.0&&$f['usableForTrial'],'unchanged values accepted after confirmed device response');
|
||||||
|
|
||||||
|
[$c,$r,$n]=dfixture();$times=[$n,$n,$n];$attempts=0;$pauses=[];
|
||||||
|
$f=D::snapshot($c,fn($id)=>D::MODBUS_DEVICE,
|
||||||
|
function()use(&$attempts){$attempts++;return $attempts>=2;},fn($id)=>$r[$id],
|
||||||
|
function()use(&$times){return array_shift($times);},function($us)use(&$pauses){$pauses[]=$us;});
|
||||||
|
dcheck($attempts===3&&$pauses===[200000],'transient busy read is retried once without relaxing confirmation');
|
||||||
|
|
||||||
[$c,$r,$n]=dfixture();$times=[$n,$n,$n];
|
[$c,$r,$n]=dfixture();$times=[$n,$n,$n];
|
||||||
dfails(fn()=>D::snapshot($c,fn($id)=>D::MODBUS_DEVICE,fn()=>false,fn($id)=>$r[$id],function()use(&$times){return array_shift($times);}),
|
dfails(fn()=>D::snapshot($c,fn($id)=>D::MODBUS_DEVICE,fn()=>false,fn($id)=>$r[$id],function()use(&$times){return array_shift($times);}),
|
||||||
'failed device read rejected');
|
'failed device read rejected');
|
||||||
|
|||||||
@@ -34,8 +34,9 @@ namespace {
|
|||||||
ck($p['status']==='preview_only'&&$p['actualBatteryW']==0,'real receiver uses corrected physical feedback rather than held -9kW');
|
ck($p['status']==='preview_only'&&$p['actualBatteryW']==0,'real receiver uses corrected physical feedback rather than held -9kW');
|
||||||
ck($p['previewBatteryW']==3000,'net target translated once to total battery demand');
|
ck($p['previewBatteryW']==3000,'net target translated once to total battery demand');
|
||||||
ck($p['canDispatch']===false&&$p['localFeedback']['b']['estimated']===true,'model feedback remains marked and preview grants no dispatch');
|
ck($p['canDispatch']===false&&$p['localFeedback']['b']['estimated']===true,'model feedback remains marked and preview grants no dispatch');
|
||||||
$feedback=['status'=>'unavailable','batteryW'=>null,'gridW'=>null];$p=json_decode($m->GetNetzfahrplanV4Vorschau(),true);
|
$feedback=['status'=>'unavailable','reason'=>'feedback_device_read_failed:grid','batteryW'=>null,'gridW'=>null];$p=json_decode($m->GetNetzfahrplanV4Vorschau(),true);
|
||||||
ck($p['status']==='unavailable'&&$p['previewBatteryW']===null,'configured feedback failure never falls back to held virtual display');
|
ck($p['status']==='unavailable'&&$p['previewBatteryW']===null,'configured feedback failure never falls back to held virtual display');
|
||||||
|
ck(str_contains($p['reason'],'feedback_unavailable:feedback_device_read_failed:grid'),'configured feedback exposes bounded root cause without credentials');
|
||||||
$configured=false;$p=json_decode($m->GetNetzfahrplanV4Vorschau(),true);
|
$configured=false;$p=json_decode($m->GetNetzfahrplanV4Vorschau(),true);
|
||||||
ck($p['status']==='preview_only'&&$p['feedbackMeaning']==='legacy_display_only_not_for_trial','unconfigured old preview stays compatible but explicitly not for control');
|
ck($p['status']==='preview_only'&&$p['feedbackMeaning']==='legacy_display_only_not_for_trial','unconfigured old preview stays compatible but explicitly not for control');
|
||||||
ck($p['canDispatch']===false,'legacy display cannot gain actuation permission');
|
ck($p['canDispatch']===false,'legacy display cannot gain actuation permission');
|
||||||
|
|||||||
Reference in New Issue
Block a user