GetValue($id), 'updated' => (int)$v['VariableUpdated'], 'changed' => (int)$v['VariableChanged'], 'parentID' => $o['ParentID'], 'ident' => $o['ObjectIdent']]; } function v4captureRun(string $stage, int $category): array { if (!function_exists('IPS_GetKernelVersion')) throw new RuntimeException('Run inside IP-Symcon only'); $c = v4captureConfig($stage); $marker = v4captureMarker($c); v4captureAssertObject($category, 0, 'ENELIX_V4_PASSIVE_CAPTURE', 0, $marker); $statusId = v4captureChild($category, 'CaptureStatus'); if ($statusId === null) throw new RuntimeException('Capture status missing'); v4captureAssertObject($statusId, $category, 'CaptureStatus', 2, $marker); $lock = 'ENELIX.V4.RawCapture.' . $c['installationId']; if (!IPS_SemaphoreEnter($lock, 0)) return ['status' => 'busy', 'controlEnabled' => false]; try { try { $record = NetzfahrplanV4Messaufnahme::capture($c, 'v4captureRead', static fn(): int => time()); v4captureKeepSnapshot($stage . '/data/mapping-' . $record['mappingSha256'] . '.json', json_encode($c, JSON_THROW_ON_ERROR | JSON_PRESERVE_ZERO_FRACTION)); v4captureKeepSnapshot($stage . '/data/inventory-' . $record['reportedInventorySha256'] . '.json', (string)file_get_contents($stage . '/USER_REPORTED_INVENTORY.json')); $path = NetzfahrplanV4Messaufnahme::append($stage . '/data', $record); $status = ['status' => 'recorded', 'capturedAt' => $record['capturedAt'], 'journal' => $path, 'sourceCount' => count($record['raw']), 'rawIssueCount' => count($record['issues']), 'accountingQuality' => $record['assessment']['status'] ?? 'unavailable', 'qualityIssues' => $record['assessment']['issues'] ?? $record['issues'], 'baseLoadCandidateW' => $record['assessment']['baseLoadCandidateW'] ?? null, 'sourceSkewSeconds' => $record['assessment']['sourceSkewSeconds'] ?? null, 'trainingEligible' => false, 'controlEnabled' => false, 'measurementBoundaryVerified' => false]; v4captureAtomicJson($stage . '/data/STATUS.json', $status); } catch (Throwable $e) { $status = ['status' => 'capture_error', 'at' => gmdate('c'), 'reason' => substr($e->getMessage(),0,250), 'trainingEligible' => false, 'controlEnabled' => false]; try { v4captureAtomicJson($stage . '/data/STATUS.json', $status); } catch (Throwable $ignored) { } } // Only this newly created diagnosis variable, never a meter or controller variable. SetValue($statusId, json_encode($status, JSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES)); return $status; } finally { IPS_SemaphoreLeave($lock); } } function v4captureStop(string $stage, int $category): void { $c = v4captureConfig($stage); $marker = v4captureMarker($c); v4captureAssertObject($category, 0, 'ENELIX_V4_PASSIVE_CAPTURE', 0, $marker); $script = v4captureChild($category, 'RawCapture'); $status = v4captureChild($category, 'CaptureStatus'); if ($script === null || $status === null) throw new RuntimeException('Capture objects missing'); v4captureAssertObject($script, $category, 'RawCapture', 3, $marker); v4captureAssertObject($status, $category, 'CaptureStatus', 2, $marker); if (!IPS_SetScriptTimer($script, 0)) throw new RuntimeException('Cannot stop capture timer'); $r = ['status' => 'stopped', 'at' => gmdate('c'), 'oldDataRetained' => true, 'controlEnabled' => false]; SetValue($status, json_encode($r, JSON_THROW_ON_ERROR)); v4captureAtomicJson($stage . '/data/STATUS.json', $r); }