feat(v4): confirm feedback with device reads
This commit is contained in:
@@ -72,28 +72,32 @@ final class NetzfahrplanV4Rueckmeldung
|
||||
{
|
||||
$c=self::configuration($config);
|
||||
if ($now<1) throw new InvalidArgumentException('feedback_time_invalid');
|
||||
$first=[];$values=[];$times=[];$physicalTimes=[];$changed=[];
|
||||
$first=[];$values=[];$times=[];$physicalTimes=[];$variableTimes=[];$changed=[];
|
||||
foreach ($c['sources'] as $s) {
|
||||
$r=$reader($s['variableId']);
|
||||
if (!is_array($r) || ($r['parentID']??null)!==$s['parentId'] || ($r['ident']??null)!==$s['ident']
|
||||
|| !is_int($r['updated']??null) || $r['updated']<1 || $r['updated']>$now
|
||||
|| !is_int($r['changed']??null) || $r['changed']<0 || $r['changed']>$r['updated']) throw new InvalidArgumentException('feedback_origin_invalid:'.$s['key']);
|
||||
$first[$s['key']]=array_intersect_key($r,array_flip(['value','updated','changed','parentID','ident']));
|
||||
|| !is_int($r['changed']??null) || $r['changed']<0 || $r['changed']>$r['updated']
|
||||
|| (in_array($s['role'],['grid','physical'],true)
|
||||
&& (!is_int($r['confirmedAt']??null) || $r['confirmedAt']<1 || $r['confirmedAt']>$now
|
||||
|| $r['updated']>$r['confirmedAt']))) throw new InvalidArgumentException('feedback_origin_invalid:'.$s['key']);
|
||||
$first[$s['key']]=array_intersect_key($r,array_flip(['value','updated','changed','parentID','ident','confirmedAt']));
|
||||
if ($s['role']==='gateway_active') {
|
||||
if (($r['value']??null)!==true) throw new InvalidArgumentException('feedback_gateway_inactive');
|
||||
} else {
|
||||
$w=self::number($r['value']??null,-1e9,1e9)*$s['factorToW'];
|
||||
$values[$s['role']]=($values[$s['role']]??0)+self::number($w,-1e9,1e9);
|
||||
}
|
||||
// An unchanged command is held STATE, not proof of a fresh sensor or an actuator watchdog.
|
||||
// VariableUpdated is retained as provenance. Freshness comes only from a successful device response.
|
||||
if (in_array($s['role'],['grid','physical'],true)) {
|
||||
if ($now-$r['updated']>$c['maxAgeSeconds']) throw new InvalidArgumentException('feedback_stale_physical:'.$s['key']);
|
||||
$times[]=$r['updated'];if ($s['role']==='physical') $physicalTimes[]=$r['updated'];
|
||||
if ($now-$r['confirmedAt']>$c['maxAgeSeconds']) throw new InvalidArgumentException('feedback_stale_physical:'.$s['key']);
|
||||
$times[]=$r['confirmedAt'];$variableTimes[]=$r['updated'];
|
||||
if ($s['role']==='physical') $physicalTimes[]=$r['confirmedAt'];
|
||||
} else $changed[$s['role']]=$r['changed'];
|
||||
}
|
||||
foreach ($c['sources'] as $s) {
|
||||
$second=$reader($s['variableId']);
|
||||
if (!is_array($second) || array_intersect_key($second,array_flip(['value','updated','changed','parentID','ident']))!==$first[$s['key']]) {
|
||||
if (!is_array($second) || array_intersect_key($second,array_flip(['value','updated','changed','parentID','ident','confirmedAt']))!==$first[$s['key']]) {
|
||||
throw new InvalidArgumentException('feedback_changed_during_read:'.$s['key']);
|
||||
}
|
||||
}
|
||||
@@ -117,7 +121,8 @@ final class NetzfahrplanV4Rueckmeldung
|
||||
return ['version'=>1,'kind'=>'local_physical_feedback','installationId'=>$c['installationId'],'assetId'=>$c['assetId'],
|
||||
'managerId'=>$c['managerId'],'batteryInstanceId'=>$c['batteryInstanceId'],'checkedAt'=>$now,
|
||||
'sourceOldestAt'=>min($times),'sourceNewestAt'=>max($times),'physicalOldestAt'=>min($physicalTimes),
|
||||
'sourceSkewSeconds'=>max($times)-min($times),'maxAgeSeconds'=>$c['maxAgeSeconds'],
|
||||
'sourceSkewSeconds'=>max($times)-min($times),'sourceVariableOldestAt'=>min($variableTimes),
|
||||
'sourceVariableNewestAt'=>max($variableTimes),'deviceReadConfirmed'=>true,'maxAgeSeconds'=>$c['maxAgeSeconds'],
|
||||
'configHash'=>self::fingerprint($c),'gridW'=>(float)$values['grid'],'physicalStorageW'=>$physical,
|
||||
'batteryW'=>$power,'externalModelW'=>$external,'trackingErrorW'=>$tracking,
|
||||
'estimated'=>$estimated,'separatelyMeasured'=>!$estimated,'usableForTrial'=>$reasons===[],
|
||||
@@ -131,6 +136,7 @@ final class NetzfahrplanV4Rueckmeldung
|
||||
if (!array_key_exists($key,$binding) || ($f[$key]??null)!==$binding[$key]) throw new InvalidArgumentException('feedback_wrong_binding');
|
||||
}
|
||||
if (($f['kind']??null)!=='local_physical_feedback' || ($f['version']??null)!==1 || ($f['usableForTrial']??null)!==true
|
||||
|| ($f['deviceReadConfirmed']??null)!==true
|
||||
|| !is_int($f['checkedAt']??null) || $f['checkedAt']>$now || $now-$f['checkedAt']>2
|
||||
|| !is_int($f['sourceOldestAt']??null) || !is_int($f['maxAgeSeconds']??null)
|
||||
|| $f['maxAgeSeconds']<1 || $f['maxAgeSeconds']>60 || $f['sourceOldestAt']>$now || $now-$f['sourceOldestAt']>$f['maxAgeSeconds']
|
||||
|
||||
Reference in New Issue
Block a user