camera - (real) remote control, brandschutz - configurable course, dashboard - vbb licensed API (a real vbb license, how cool)

This commit is contained in:
2026-09-08 18:21:51 +02:00
parent 3f98cf2663
commit ce90820094
24 changed files with 3044 additions and 2065 deletions
@@ -0,0 +1,120 @@
<?php
declare(strict_types=1);
const CAMERA_REMOTE_ACTIONS = [
'power' => ['entity_id' => 'script.kamera_on_off', 'calls' => 1],
'total' => ['entity_id' => 'script.kamera_totale', 'calls' => 2],
'lectern' => ['entity_id' => 'script.kamera_pult', 'calls' => 2],
'altar' => ['entity_id' => 'script.kamera_altar', 'calls' => 2],
'chancel' => ['entity_id' => 'script.kamera_altarraum', 'calls' => 2],
'before_altar' => ['entity_id' => 'script.kamera_vor_dem_altar', 'calls' => 2],
'flowers' => ['entity_id' => 'script.kamera_blumenschmuck', 'calls' => 2],
'choir' => ['entity_id' => 'script.kamera_chor', 'calls' => 2],
'piano' => ['entity_id' => 'script.kamera_klavier', 'calls' => 2],
'organ' => ['entity_id' => 'script.kamera_orgel', 'calls' => 2],
'orchestra' => ['entity_id' => 'script.kamera_orchester_kinderchor', 'calls' => 2],
];
function camera_remote_is_authorized(array $groups): bool
{
return !empty($groups['Technik']) || !empty($groups['Admin']);
}
function camera_remote_csrf_token(): string
{
return $_SESSION['camera_remote_csrf_token'] ??= bin2hex(random_bytes(32));
}
function camera_remote_verify_csrf(mixed $token): void
{
$sessionToken = $_SESSION['camera_remote_csrf_token'] ?? '';
if (!is_string($token) || !is_string($sessionToken) || $sessionToken === '' || !hash_equals($sessionToken, $token)) {
throw new InvalidArgumentException('Ungültige Anfrage. Bitte laden Sie die Seite neu.');
}
}
/** @return array{base_url: string, token: string} */
function camera_remote_config(): array
{
$baseUrl = rtrim(trim((string) getenv('HA_BASE_URL')), '/');
$token = trim((string) getenv('HA_TOKEN'));
$scheme = strtolower((string) parse_url($baseUrl, PHP_URL_SCHEME));
$host = parse_url($baseUrl, PHP_URL_HOST);
if ($baseUrl === '' || $token === '') {
throw new RuntimeException('Home Assistant ist noch nicht konfiguriert.');
}
if (!in_array($scheme, ['http', 'https'], true) || !is_string($host) || $host === '') {
throw new RuntimeException('Die Home-Assistant-Adresse ist ungültig.');
}
return ['base_url' => $baseUrl, 'token' => $token];
}
function camera_remote_is_configured(): bool
{
try {
camera_remote_config();
return true;
} catch (RuntimeException) {
return false;
}
}
function camera_remote_request(string $path, ?array $payload = null): void
{
$config = camera_remote_config();
$handle = curl_init($config['base_url'] . $path);
if ($handle === false) {
throw new RuntimeException('Die Verbindung zu Home Assistant konnte nicht vorbereitet werden.');
}
$options = [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $config['token'],
'Content-Type: application/json',
'Accept: application/json',
],
CURLOPT_CONNECTTIMEOUT => 3,
CURLOPT_TIMEOUT => 8,
CURLOPT_FOLLOWLOCATION => false,
];
if ($payload !== null) {
$options[CURLOPT_POST] = true;
$options[CURLOPT_POSTFIELDS] = json_encode($payload, JSON_THROW_ON_ERROR);
}
curl_setopt_array($handle, $options);
try {
$response = curl_exec($handle);
$status = (int) curl_getinfo($handle, CURLINFO_HTTP_CODE);
$error = curl_error($handle);
} finally {
curl_close($handle);
}
if ($response === false || $status < 200 || $status >= 300) {
error_log('Camera Remote: Home Assistant antwortete mit ' . ($error !== '' ? $error : 'HTTP ' . $status));
throw new RuntimeException('Home Assistant konnte den Kamerabefehl nicht ausführen.');
}
try {
json_decode($response, true, 512, JSON_THROW_ON_ERROR);
} catch (JsonException $exception) {
throw new RuntimeException('Home Assistant hat unerwartet geantwortet.', 0, $exception);
}
}
function camera_remote_run(string $action): void
{
$command = CAMERA_REMOTE_ACTIONS[$action] ?? null;
if (!is_array($command)) {
throw new InvalidArgumentException('Unbekannter Kamerabefehl.');
}
for ($call = 0; $call < $command['calls']; $call++) {
camera_remote_request('/api/services/script/turn_on', ['entity_id' => $command['entity_id']]);
}
}