getTimestamp(); } private static function number(mixed $value): float { if ((!is_float($value) && !is_int($value)) || !is_finite((float)$value)) { throw new InvalidArgumentException('Finite numeric value required'); } return (float)$value; } public static function currentPoint(array $plan, int $now, int $revision, bool $shadowInspection=false): ?array { if (($plan['schemaVersion']??null)!==2 || ($plan['configRevision']??null)!==$revision || ($plan['executable']??false)!==true || !is_array($plan['points']??null) || !in_array($plan['status']??'', ['optimal','feasible_time_limit'], true) || !is_string($plan['planId']??null) || $plan['planId']==='') return null; if ($shadowInspection ? ($plan['runMode']??'')!=='shadow' : (($plan['runMode']??'')!=='live' || ($plan['liveEnabled']??false)!==true)) return null; try { $age=$now-self::stamp($plan['generatedAt']); if ($age < -30 || $age>900 || self::stamp($plan['validFrom'])>$now || self::stamp($plan['validUntil'])<=$now) return null; $selected=null; $previousEnd=null; foreach ($plan['points'] as $p) { $start=self::stamp($p['time']); $end=self::stamp($p['validUntil']); self::number($p['gridTargetW']); if ($end<=$start || $end-$start>300 || ($previousEnd!==null && $previousEnd!==$start)) return null; $previousEnd=$end; if ($start<=$now && $now<$end) $selected=$p; } return $selected; } catch (\Throwable $e) { return null; } } public static function correction(array $point, float $grid, float $battery, float $chargeAvailable, float $dischargeAvailable, ?float $hardImportLimit=null): array { foreach ([$grid,$battery,$chargeAvailable,$dischargeAvailable] as $v) self::number($v); if ($chargeAvailable<0 || $dischargeAvailable<0) throw new InvalidArgumentException('Invalid available power'); $target=self::number($point['gridTargetW']??null); if ($hardImportLimit!==null) { if (self::number($hardImportLimit)<0) throw new InvalidArgumentException('Negative hard limit'); $target=min($target,$hardImportLimit); } $raw=$battery+$target-$grid; if (abs($target)>1) { $raw=match ($point['intent']??'') { 'gridCharge'=>max(0.,$raw), 'pvCharge'=>max(0.,min($battery-$grid,$raw)), 'discharge','export'=>min(0.,$raw), 'hold'=>0., default=>throw new InvalidArgumentException('Unknown plan intent'), }; } if ($hardImportLimit!==null) $raw=min($raw,$battery+$hardImportLimit-$grid); $wanted=min($chargeAvailable,max(-$dischargeAvailable,$raw)); $expectedGrid=$grid+$wanted-$battery; return ['batteryTargetW'=>$wanted,'gridTargetW'=>$target,'limited'=>abs($wanted-$raw)>1, 'expectedGridW'=>$expectedGrid,'trackingErrorW'=>$expectedGrid-$target]; } }