Dieses Kamera-Widget ist Teil meines Pokemon-Floorplans und besteht aus mehreren Bausteinen und ermöglicht es, Live-Bilder und Streams aus verschiedenen Kameras direkt in der OpenHAB-Oberfläche darzustellen.
Zusätzlich können – je nach Kamera – Steuerungsfunktionen wie PTZ (Pan, Tilt, Zoom), Presets, oder Alarmsignale (Bewegung/Audio) eingebunden werden.
Als Eingaben können KI-gestützte RaspberryPi-Kameras, ESP32Cam oder andere IP-Kameras dienen.
Die Umsetzung besteht aus vier Teilen:
- Symbol-Widget zum Öffnen des Kamera-Popups
- Darstellungs-Widget mit Steuerung und Stream-Ausgabe
- HLS-Player (für Kameras mit HLS-Stream)
- MJPEG-Player (für einfache MJPEG-Streams)
Symbol-Widget (Popup-Trigger)
Dieses kleine Widget ist nur ein Button mit Kamerasymbol, das ein Popup öffnet.
Im Code wird definiert:
- welche URLs für den Stream (MJPEG oder JPEG-Vorschau) genutzt werden,
- welche Items für Steuerung oder Alarme angebunden sind.
Für Laien erklärt:
Das Symbol ist der „Eingang“ zum Kamera-Fenster. Klickt man darauf, wird das größere Darstellungs-Widget geöffnet.
Widget-Code
uid: ha_cam_popup
tags: []
props:
parameters:
- label: MJPEG URL
name: camera
required: true
type: TEXT
- label: JPEG URL
name: preview
required: true
type: TEXT
- context: item
label: PTZ-Item
name: ptz
required: false
type: TEXT
- context: item
label: Pan-Item
name: pan
required: false
type: TEXT
- context: item
label: GoToPreset-Item
name: gotopreset
required: false
type: TEXT
- context: item
label: Motion-Alarm-Item
name: motionalarm
required: false
type: TEXT
- context: item
label: Audio-Alarm-Item
name: audioalarm
required: false
type: TEXT
timestamp: Jun 17, 2025, 1:33:33 AM
component: oh-button
config:
action: popup
actionModal: widget:ha_cam
actionModalConfig:
audioalarm: =props.audioalarm
camera: =props.camera
gotopreset: =props.gotopreset
motionalarm: =props.motionalarm
pan: =props.pan
preview: =props.preview
ptz: =props.ptz
iconF7: videocam_circle
iconSize: 30
slots: {}
Darstellungs-Widget (Kamera-Ansicht)
Hier passiert die eigentliche Magie:
- Der Stream (HLS oder MJPEG) wird im OpenHAB-Frontend angezeigt.
- Steuerungselemente (Pfeile, Stop-Symbol, Presets) erscheinen nur dann, wenn die entsprechenden Items gesetzt sind.
- Bewegungs- oder Audio-Alarm werden als kleine Symbole eingeblendet.
Für Laien erklärt:
Das ist das große Fenster, in dem man das Kamerabild sieht. Links/rechts/hoch/runter-Pfeile steuern die Kamera, falls diese PTZ unterstützt. Zusätzlich gibt es Symbole für Bewegung und Ton, die bei einem Alarm sichtbar werden.
Widget-Code
uid: ha_cam
tags: []
props:
parameters:
- label: MJPEG URL
name: camera
required: true
type: TEXT
- label: JPEG URL
name: preview
required: true
type: TEXT
- context: item
label: PTZ-Item
name: ptz
required: false
type: TEXT
- context: item
label: Pan-Item
name: pan
required: false
type: TEXT
- context: item
label: GoToPreset-Item
name: gotopreset
required: false
type: TEXT
- context: item
label: Motion-Alarm-Item
name: motionalarm
required: false
type: TEXT
- context: item
label: Audio-Alarm-Item
name: audioalarm
required: false
type: TEXT
timestamp: Jul 5, 2025, 12:17:58 AM
component: f7-card
config:
style:
--f7-card-margin-horizontal: 0px
height: calc(100% - 5px)
margin: 0
width: 100%
slots:
default:
- component: oh-link
config:
action: command
actionCommand: left
actionItem: =props.ptz
class: card-prevent-open
iconF7: arrow_left
iconSize: 40
style:
color: var(--f7-card-header-text-color)
margin: 0 0.4em
opacity: 0.5
visible: "=(props.ptz) ? 'true' : 'false'"
- component: oh-link
config:
action: command
actionCommand: up
actionItem: =props.ptz
class:
- card-prevent-open
iconF7: arrow_up
iconSize: 40
style:
color: var(--f7-card-header-text-color)
margin: 0 0.4em
opacity: 0.5
visible: "=(props.ptz) ? 'true' : 'false'"
- component: oh-link
config:
action: command
actionCommand: false
actionItem: =props.pan
class:
- card-prevent-open
iconF7: stop_circle
iconSize: 40
style:
color: var(--f7-card-header-text-color)
margin: 0 0.4em
opacity: 0.5
visible: "=(props.pan) ? 'true' : 'false'"
- component: oh-link
config:
action: command
actionCommand: down
actionItem: =props.ptz
class: card-prevent-open
iconF7: arrow_down
iconSize: 40
style:
color: var(--f7-card-header-text-color)
margin: 0 0.4em
opacity: 0.5
visible: "=(props.ptz) ? 'true' : 'false'"
- component: oh-link
config:
action: command
actionCommand: right
actionItem: =props.ptz
class: card-prevent-open
iconF7: arrow_right
iconSize: 40
style:
color: var(--f7-card-header-text-color)
margin: 0 0.4em
opacity: 0.5
visible: "=(props.ptz) ? 'true' : 'false'"
- component: oh-link
config:
action: options
actionItem: =props.gotopreset
class: card-prevent-open
iconF7: list_number
iconSize: 40
style:
color: var(--f7-card-header-text-color)
margin: 0 0.4em
opacity: 0.5
visible: "=(props.gotopreset) ? 'true' : 'false'"
- component: oh-link
config:
iconF7: eye
iconSize: 24
style:
color: var(--f7-card-header-text-color)
opacity: "=(@@props.motionalarm === 'ON') ? '0.5' : '0'"
position: absolute
right: 0.2rem
top: 7.8rem
visible: "=(props.motionalarm) ? 'true' : 'false'"
- component: oh-link
config:
iconF7: ear
iconSize: 24
style:
color: var(--f7-card-header-text-color)
left: 0rem
opacity: "=(@@props.audioalarm === 'ON') ? '0.5' : '0'"
position: absolute
top: 7.7rem
visible: "=(props.audioalarm) ? 'true' : 'false'"
- component: oh-webframe
config:
height: 100%
lazy: true
src: |
=props.camera.endsWith('.m3u8') ?
"/static/hls_player.html?src=" + props.camera
: "/static/mjpeg_player.html?src=" + props.camera
style:
margin: 0px
HLS-Player (HTML-Seite)
Dieser Player ist für moderne Kameras, die einen HLS-Stream ausgeben.
HLS ist sehr Bandbreitenschonen, hat aber durch seine Technik eine leichte Verzögerung.
Für Laien erklärt:
HLS ist eine Streaming-Technik, wie sie auch Netflix oder YouTube verwenden. Dadurch wird das Video gestückelt übertragen und läuft stabiler über das Internet.
Das Darstellungs-Widget benötigt es und versucht es unter /static/hls_player.html zu erreichen. In vielen Fällen entspricht das /etc/openhab/html/hls_player.html auf einem Linux-Filesystem deines OpenHAB-Servers.
Widget-Code
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<title>HLS Video</title>
<script src="hls.js"></script>
</head>
<body style='background: black;'>
<video id="video" controls width="100%"></video>
<script>
// URL-Parameter lesen
// https://deine-seite.de/video.html?src=https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8
const params = new URLSearchParams(window.location.search);
const videoSrc = params.get('src');
if (videoSrc) {
const video = document.getElementById('video');
if (Hls.isSupported()) {
const hls = new Hls({
liveSyncDurationCount: 2
});
hls.loadSource(videoSrc);
hls.attachMedia(video);
} else if (video.canPlayType('application/vnd.apple.mpegurl')) {
video.src = videoSrc;
}
} else {
document.body.insertAdjacentHTML('beforeend', '<p>Kein Video-Link angegeben.</p>');
}
</script>
</body>
</html>
MJPEG-Player (HTML-Seite)
Dieser Player ist für einfache Kameras, die nur MJPEG ausgeben können.
Das Bild wird dabei als Endlosschleife einzelner JPEG-Bilder übertragen – eine ältere, aber sehr kompatible Technik.
MJPEG verbraucht normalerweise einen großen Teil an Bandbreite.
Für Laien erklärt:
Man sieht quasi eine schnelle Bildergalerie, die wie ein Video wirkt. Es gibt zusätzlich einen Vollbild-Knopf, um das Kamerabild zu maximieren.
Das Darstellungs-Widget benötigt es und versucht es unter /static/mjpeg_player.html zu erreichen. In vielen Fällen entspricht das /etc/openhab/html/mjpeg_player.html auf einem Linux-Filesystem deines OpenHAB-Servers.
Widget-Code
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Einfacher MJPEG-Player mit URL-Parameter</title>
<style>
body {
margin: 0;
font-family: Arial, sans-serif;
background-color: black;
}
#player-container {
position: relative;
max-width: 640px;
margin: 0;
border: 0;
border-radius: 6px;
overflow: hidden;
background: #000;
}
#mjpeg-frame {
width: 100%;
border: none;
display: block;
}
#controls {
text-align: center;
margin: 10px auto;
}
button {
padding: 8px 16px;
font-size: 1rem;
cursor: pointer;
border-radius: 4px;
border: none;
background-color: #007bff;
color: white;
}
button:hover {
background-color: #0056b3;
}
#player-container.fullscreen {
position: fixed;
top: 0;
left: 0;
width: 100vw !important;
height: 100vh !important;
max-width: none !important;
aspect-ratio: auto !important;
z-index: 9999;
border-radius: 0;
}
</style>
</head>
<body>
<div id="player-container">
<img
id="mjpeg-frame"
src=""
controls
/>
</div>
<div id="controls">
<button id="toggle-fullscreen">Maximieren</button>
</div>
<script>
// Funktion um URL-Parameter auszulesen
function getUrlParameter(name) {
name = name.replace(/[\[\]]/g, '\\$&');
const regex = new RegExp('[?&]' + name + '(=([^]*)|&|#|$)');
const results = regex.exec(window.location.href);
if (!results) return null;
if (!results[2]) return '';
return decodeURIComponent(results[2].replace(/\+/g, ' '));
}
const player = document.getElementById('player-container');
const iframe = document.getElementById('mjpeg-frame');
const btn = document.getElementById('toggle-fullscreen');
// MJPEG URL aus Parameter lesen
const mjpegUrl = getUrlParameter('src') || 'http://dein-mjpeg-stream-url/mjpeg';
iframe.src = mjpegUrl;
btn.addEventListener('click', () => {
if (!document.fullscreenElement) {
if (player.requestFullscreen) {
player.requestFullscreen();
} else if (player.webkitRequestFullscreen) {
player.webkitRequestFullscreen();
} else if (player.msRequestFullscreen) {
player.msRequestFullscreen();
}
} else {
if (document.exitFullscreen) {
document.exitFullscreen();
} else if (document.webkitExitFullscreen) {
document.webkitExitFullscreen();
} else if (document.msExitFullscreen) {
document.msExitFullscreen();
}
}
});
document.addEventListener('fullscreenchange', () => {
if (document.fullscreenElement) {
player.classList.add('fullscreen');
} else {
player.classList.remove('fullscreen');
}
});
</script>
</body>
</html>
Pingback: Interaktiver Floorplan in OpenHAB mit Effekten