Anleitungen erstellt und Bildrate einstellbar

This commit is contained in:
2026-05-25 17:09:31 +02:00
parent 3207b58772
commit 023017d1fb
10 changed files with 663 additions and 36 deletions
+39 -9
View File
@@ -235,20 +235,35 @@
<div class="panel">
<div class="p-hdr">
<div class="p-dot"></div>
<span class="p-lbl">Stream-Aufl&ouml;sung</span>
<span class="p-lbl">Kamera-Stream</span>
<span class="p-tag">Konfiguration</span>
</div>
<div class="dropdown-row">
<label>Profil</label>
<select id="camera_profile_select" class="ctrl-select" onchange="setCameraProfile(this.value)">
<option value="640x480">640 x 480 &middot; 4:3 &middot; gr&ouml;&szlig;tes Sichtfeld</option>
<option value="1024x768">1024 x 768 &middot; 4:3 &middot; mehr Details</option>
<option value="1296x972">1296 x 972 &middot; 4:3 &middot; noch mehr Details</option>
<option value="640x480">640 x 480</option>
<option value="1024x768">1024 x 768</option>
<option value="1296x972">1296 x 972</option>
</select>
<span id="camera_profile_status" class="ctrl-status"></span>
</div>
<div class="dropdown-row">
<label>Bildrate</label>
<select id="camera_fps_select" class="ctrl-select" onchange="setCameraFps(this.value)">
<option value="1">1 fps</option>
<option value="2">2 fps</option>
<option value="3">3 fps</option>
<option value="4">4 fps</option>
<option value="5">5 fps</option>
<option value="7">7 fps</option>
<option value="10">10 fps</option>
<option value="20">20 fps</option>
</select>
</div>
<div class="dropdown-status-row">
<span id="camera_profile_status" class="ctrl-status ctrl-status_block"></span>
</div>
<div class="copy-text camera-copy">
Die neue Aufl&ouml;sung wird gespeichert und der Kameradienst automatisch neu gestartet. Der Videostream ist dabei f&uuml;r einen kurzen Moment unterbrochen.
Aufl&ouml;sung und Bildrate werden gespeichert. Danach startet der Kameradienst automatisch neu. Der Videostream ist dabei f&uuml;r einen kurzen Moment unterbrochen.
</div>
<div class="act-grid act-grid_single">
<button class="act-btn subtle full" onclick="refreshCameraPreview()">Vorschau neu laden</button>
@@ -2353,8 +2368,15 @@
function renderCameraConfig(data) {
var camera = data && data.camera ? data.camera : {};
var selectedProfile = camera.selected_profile || '';
var select = document.getElementById('camera_profile_select');
if (select && selectedProfile) select.value = selectedProfile;
var profileSelect = document.getElementById('camera_profile_select');
var fpsSelect = document.getElementById('camera_fps_select');
if (profileSelect && selectedProfile) profileSelect.value = selectedProfile;
if (fpsSelect && Array.isArray(camera.fps_options) && camera.fps_options.length) {
fpsSelect.innerHTML = camera.fps_options.map(function(option) {
return '<option value="' + option + '">' + option + ' fps</option>';
}).join('');
}
if (fpsSelect && camera.fps) fpsSelect.value = String(camera.fps);
setText('camera_profile_label', selectedProfile || '--');
setText('camera_resolution_label', (camera.width && camera.height) ? (camera.width + ' x ' + camera.height) : '--');
setText('camera_fps_label', camera.fps ? (camera.fps + ' fps') : '--');
@@ -2369,13 +2391,15 @@
}
async function setCameraProfile(profileId) {
var fpsSelect = document.getElementById('camera_fps_select');
var fps = fpsSelect ? Number(fpsSelect.value || 10) : 10;
var statusEl = document.getElementById('camera_profile_status');
if (statusEl) statusEl.textContent = 'Speichere…';
try {
var response = await fetch(adminApiBase + '/api/camera-config', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({profile: profileId})
body: JSON.stringify({profile: profileId, fps: fps})
});
var data = await response.json();
if (!response.ok) throw new Error(data.status || 'Fehler');
@@ -2390,6 +2414,12 @@
}
}
function setCameraFps(fps) {
var profileSelect = document.getElementById('camera_profile_select');
var profileId = profileSelect ? profileSelect.value : '1296x972';
setCameraProfile(profileId);
}
async function runRumble() {
try {
var response = await fetch(adminApiBase + '/api/rumble', { method: 'POST' });
+3 -1
View File
@@ -1181,14 +1181,16 @@ body.page-admin .link-btn:hover{ background:rgba(0,136,255,.14); color:#55AAFF;
/* ── DROPDOWN CONTROL ───────────────────────────── */
body.page-admin .dropdown-row{ display:flex; align-items:center; gap:10px; padding:10px 12px; }
body.page-admin .dropdown-row label{ font-size:9px; letter-spacing:1.6px; color:rgba(210,255,235,.9); text-transform:uppercase; min-width:54px; }
body.page-admin .dropdown-status-row{ padding:0 12px 10px 76px; }
body.page-admin .ctrl-select{
font-family:var(--mono); font-size:10px; letter-spacing:1px;
color:rgba(245,255,250,.96); background:#050A14;
border:1px solid rgba(120,255,205,.36); padding:7px 10px;
cursor:pointer; outline:none; flex:1;
cursor:pointer; outline:none; flex:0 0 190px; width:190px; max-width:190px;
}
body.page-admin .ctrl-select:focus{ border-color:rgba(0,255,145,.5); }
body.page-admin .ctrl-status{ font-size:9px; letter-spacing:1.2px; color:rgba(225,255,242,.92); white-space:nowrap; min-width:64px; text-align:right; }
body.page-admin .ctrl-status_block{ display:block; min-width:0; max-width:190px; text-align:left; white-space:normal; line-height:1.45; }
body.page-admin .camera-copy{
font-size:10px; color:rgba(205,255,235,.86); letter-spacing:.4px; line-height:1.6;
padding:8px 12px; border-bottom:1px solid rgba(0,255,145,.05);
+59 -3
View File
@@ -57,7 +57,7 @@
<div class="panel" id="cam-panel">
<div class="p-hdr">
<div class="p-dot"></div>
<span class="p-lbl">Kamera — CAM01</span>
<span class="p-lbl">Kamera - CAM01</span>
<span class="p-tag">Live Stream</span>
</div>
<div class="cam-port">
@@ -288,6 +288,10 @@ var gpsStatusListener = null;
var gpsDiagnosticsListener = null;
var rosReconnectTimer = null;
var rosDisconnectTimer = null;
var cameraReconnectTimer = null;
var cameraRecoveryTimers = [];
var lastCameraServiceState = "";
var lastCameraSettingsVersion = null;
var publishTimer = null;
var statusTimer = null;
var axes = [0, 0, 0, 0, 0, 0];
@@ -926,6 +930,43 @@ function refreshRosSocketState() {
}
}
function clearCameraReconnect() {
if (!cameraReconnectTimer) return;
window.clearTimeout(cameraReconnectTimer);
cameraReconnectTimer = null;
}
function clearCameraRecoveryTimers() {
cameraRecoveryTimers.forEach(function (timerId) {
window.clearTimeout(timerId);
});
cameraRecoveryTimers = [];
}
function reloadCameraStream() {
var video = document.getElementById("video_output");
if (!video) return;
video.src = "//" + hostUrl + ":8083/stream.mjpg";
}
function scheduleCameraReconnect(delayMs) {
if (cameraReconnectTimer) return;
cameraReconnectTimer = window.setTimeout(function () {
cameraReconnectTimer = null;
reloadCameraStream();
}, delayMs || 2500);
}
function triggerCameraRecovery() {
clearCameraReconnect();
clearCameraRecoveryTimers();
[1200, 2600, 4200, 6500].forEach(function (delayMs) {
cameraRecoveryTimers.push(window.setTimeout(function () {
reloadCameraStream();
}, delayMs));
});
}
function updateUndervoltageDisplay(text, state) {
var el = document.getElementById("stat-undervoltage");
if (!el) {
@@ -952,6 +993,8 @@ function updateUndervoltageDisplay(text, state) {
function updateServiceState(payload) {
var system = payload.system || {};
var services = payload.services || {};
var cameraServiceState = String(services.camera || "");
var cameraSettingsVersion = Number(services.camera_settings_version || 0);
setText("stat-wifi", system.wifi || "unbekannt");
setText("stat-ips", system.ips || "unbekannt");
updateUndervoltageDisplay(system.undervoltage || "unbekannt", system.undervoltage_state || "unknown");
@@ -963,7 +1006,17 @@ function updateServiceState(payload) {
document.getElementById("bar-cpu").style.width = percentTextToWidth(system.cpu_usage);
document.getElementById("bar-mem").style.width = percentTextToWidth(system.memory_usage);
document.getElementById("bar-temp").style.width = temperatureTextToWidth(system.cpu_temperature);
setText("cam-state", services.camera === "active" ? "Online" : services.camera || "unbekannt");
if (cameraServiceState === "active" && lastCameraServiceState && lastCameraServiceState !== "active") {
setText("cam-state", "Verbinde...");
triggerCameraRecovery();
}
if (lastCameraSettingsVersion !== null && cameraSettingsVersion !== lastCameraSettingsVersion) {
setText("cam-state", "Verbinde...");
triggerCameraRecovery();
}
lastCameraServiceState = cameraServiceState;
lastCameraSettingsVersion = cameraSettingsVersion;
setText("cam-state", cameraServiceState === "active" ? "Online" : cameraServiceState || "unbekannt");
}
function fetchSystemStatus() {
@@ -1236,8 +1289,10 @@ window.addEventListener("load", function () {
window.setInterval(renderTrajectoryOverlay, 250);
var video = document.getElementById("video_output");
video.src = "//" + hostUrl + ":8083/stream.mjpg";
reloadCameraStream();
video.addEventListener("load", function () {
clearCameraReconnect();
clearCameraRecoveryTimers();
setText("cam-state", "Online");
setText("cam-host", hostUrl);
setText("cam-live-text", "LIVE");
@@ -1254,6 +1309,7 @@ window.addEventListener("load", function () {
if (liveIndicator) {
liveIndicator.classList.add("is-offline");
}
scheduleCameraReconnect(2500);
});
updateCameraFrameLayout();
window.addEventListener("resize", updateCameraFrameLayout);