feat(workflow): publish verified forecast controls and consolidated docs
Tests / test (push) Successful in 1m1s

Approved by Daniel Haefliger for develop and beta. Author dh_Agent, authenticated account dh. Preserve published battery, charging and overall Energy Pie changes. No deployment or plant control authorization.
This commit is contained in:
dh
2026-10-08 10:02:58 +00:00
parent af69151165
commit 74a2d6c685
82 changed files with 5054 additions and 617 deletions
+22 -15
View File
@@ -41,9 +41,7 @@ trait ManagerNetzfahrplanV4EmpfangTrait
private function konfiguriereNetzfahrplanV4Vorschau(): void
{
$enabled = $this->ReadPropertyBoolean('NetzfahrplanV4EmpfangAktiv')
&& $this->ReadPropertyBoolean('NetzfahrplanV4SchattenAktiv')
&& !$this->ReadPropertyBoolean('NetzfahrplanAktiv');
$enabled = $this->prognoseLernenAktiv();
$this->SetTimerInterval('NetzfahrplanV4VorschauAktualisieren', $enabled ? 10000 : 0);
$this->SetTimerInterval('NetzfahrplanV4EmpfangAktualisieren', $enabled ? 10000 : 0);
$this->aktualisiereNetzfahrplanV4Vorschau();
@@ -52,26 +50,31 @@ trait ManagerNetzfahrplanV4EmpfangTrait
/** Called before the next operation upload, so the preceding completed plan can be seen. */
private function empfangeNetzfahrplanV4(): void
{
if (!$this->ReadPropertyBoolean('NetzfahrplanV4EmpfangAktiv')) return;
if (!$this->prognoseLernenAktiv()) return;
$lock = 'ENELIX.V4.Receive.' . $this->InstanceID;
if (!IPS_SemaphoreEnter($lock, 0)) return;
try {
$now = time();
if ($now < $this->ReadAttributeInteger('NetzfahrplanV4NaechsterAbruf')) return;
$session = method_exists($this, 'v4ManagerTestSession') ? $this->v4ManagerTestSession() : [];
$interval = ($session['kind'] ?? null) === 'local_active_test_consent' ? 45 : 60;
$interval = $this->ReadPropertyBoolean('NetzfahrplanRegelungAktiv')
|| ($session['kind'] ?? null) === 'local_active_test_consent' ? 45 : 60;
$this->WriteAttributeInteger('NetzfahrplanV4NaechsterAbruf', $now + $interval);
if (!$this->ReadPropertyBoolean('NetzfahrplanV4SchattenAktiv') || $this->ReadPropertyBoolean('NetzfahrplanAktiv')) {
throw new RuntimeException('Schatten-Sender fehlt oder alter Fahrplan ist eingeschaltet.');
}
if (!$this->berechtigungLizenziert(Lizenzpruefung::NETZFAHRPLAN)) {
throw new RuntimeException('Netzfahrplanberechtigung fehlt.');
}
$response = $this->netzfahrplanV4EmpfangHttp('GET', '');
$receipt = time();
if (!$this->ReadPropertyBoolean('NetzfahrplanV4EmpfangAktiv') || $this->ReadPropertyBoolean('NetzfahrplanAktiv')) {
if (!$this->prognoseLernenAktiv()) {
throw new RuntimeException('Freigabe waehrend des Abrufs geaendert.');
}
if (($response['installationId'] ?? null) !== $this->ReadAttributeString('LizenzInstallationID')) {
throw new RuntimeException('Serverantwort gehoert zu einer anderen Installation.');
}
if (is_array($response['workflow'] ?? null)) {
$this->WriteAttributeString('PrognoseV4Serverstatus', json_encode(
['receivedAt' => $receipt, 'workflow' => $response['workflow']], JSON_THROW_ON_ERROR));
}
// Validate the candidate before replacing the last accepted envelope. During an
// active test a failed refresh may keep the previous envelope, but its original
// receivedAt is never changed, so invalid traffic can never extend its lifetime.
@@ -81,7 +84,7 @@ trait ManagerNetzfahrplanV4EmpfangTrait
$preview = NetzfahrplanV4Planpruefung::vorschau($checked, $operation, $actual, time());
// Only the known, non-secret protocol envelope is cached.
$keys = ['receiverProtocolVersion', 'installationId', 'checkedAt', 'liveEnabled', 'fresh',
'pending', 'settings', 'plan', 'families', 'lastRun', 'controlledTrial'];
'pending', 'settings', 'plan', 'families', 'lastRun', 'controlledTrial', 'workflow'];
$safe = array_intersect_key($response, array_flip($keys));
$this->WriteAttributeString('NetzfahrplanV4Empfang', json_encode(
['receivedAt' => $receipt, 'response' => $safe], JSON_THROW_ON_ERROR));
@@ -107,7 +110,8 @@ trait ManagerNetzfahrplanV4EmpfangTrait
$previous = json_decode($this->ReadAttributeString('NetzfahrplanV4Empfang'), true);
if (is_array($previous) && is_int($previous['receivedAt'] ?? null)) {
$session = method_exists($this, 'v4ManagerTestSession') ? $this->v4ManagerTestSession() : [];
$keepPrevious = (($session['kind'] ?? null) === 'local_active_test_consent')
$keepPrevious = ($this->ReadPropertyBoolean('NetzfahrplanRegelungAktiv')
|| ($session['kind'] ?? null) === 'local_active_test_consent')
&& $previous['receivedAt'] <= time()
&& time() - $previous['receivedAt'] <= NetzfahrplanV4Planpruefung::MAX_RECEIPT_AGE;
}
@@ -181,11 +185,15 @@ trait ManagerNetzfahrplanV4EmpfangTrait
&& IPS_GetProperty($controller,'LadezustandVariableID')===($asset['SOCVariableID']??null)) $controllers[]=$controller;
}
if (count($controllers)>1) throw new RuntimeException('feedback_controller_ambiguous');
if (count($controllers)===1 && function_exists('ENELIX_GetV4BatterieRueckmeldung')) {
if (count($controllers)===1 && function_exists('ENELIX_GetV4BatterieRueckmeldungCache')) {
$controller=$controllers[0];$configured=IPS_GetProperty($controller,'NetzfahrplanV4RueckmeldungKonfiguration');
if (in_array(trim((string)$configured),['','{}'],true)
&& method_exists($this,'GetV4RueckmeldungKonfiguration')) {
$configured=$this->GetV4RueckmeldungKonfiguration($controller);
}
if (is_string($configured) && !in_array(trim($configured),['','{}'],true)) {
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_GetV4BatterieRueckmeldungCache($controller),true,32,JSON_THROW_ON_ERROR);
$feedbackNow=time();
if (($f['status']??null)==='unavailable') {
$reason=is_string($f['reason']??null)?$f['reason']:'unknown';
@@ -222,10 +230,9 @@ trait ManagerNetzfahrplanV4EmpfangTrait
public function GetNetzfahrplanV4Vorschau(): string
{
try {
if (!$this->ReadPropertyBoolean('NetzfahrplanV4EmpfangAktiv') || !$this->ReadPropertyBoolean('NetzfahrplanV4SchattenAktiv')) {
if (!$this->prognoseLernenAktiv()) {
throw new RuntimeException('V4-Empfang ausgeschaltet.');
}
if ($this->ReadPropertyBoolean('NetzfahrplanAktiv')) throw new RuntimeException('Alter Fahrplan ist aktiv; V4-Vorschau gesperrt.');
if (!$this->berechtigungLizenziert(Lizenzpruefung::NETZFAHRPLAN)) throw new RuntimeException('Netzfahrplanberechtigung fehlt.');
$envelope = json_decode($this->ReadAttributeString('NetzfahrplanV4Empfang'), true, 64, JSON_THROW_ON_ERROR);
if (!is_array($envelope['response'] ?? null) || !is_int($envelope['receivedAt'] ?? null)) {