Bezoekerslogboek in Home Assistant – slimme voordeur snapshots in een eigen gallery
In deze blog laat ik zien hoe ik een bezoekerslogboek heb gebouwd in Home Assistant. Bij beweging aan de voordeur maakt mijn Reolink-camera automatisch een snapshot, die wordt opgeslagen in een map /www/visitors. Op mijn tablet heb ik een mooi fullscreen gallery-overzicht met een datumselector en een lightbox om door de foto’s te bladeren. Inclusief volledige YAML en HTML, zodat je alles één-op-één kunt overnemen. Gevoelige gegevens (zoals IP-adressen en wachtwoorden) laat ik bewust weg of verwijs ik via !secret.
Waarom ik een bezoekerslogboek wilde
Bij iedere beweging aan de voordeur maakte mijn camera al een snapshot en kreeg ik een melding op mijn telefoon. Handig, maar ik wilde méér:
-
Alle bezoekers van de dag in één visueel logboek.
-
Op mijn Home Assistant-tablet gewoon even door de dag heen kunnen scrollen.
-
Makkelijk een paar dagen terugkijken (maximaal enkele dagen, geen NAS-achtige oplossing).
-
Een nette, fullscreen gallery met lightbox en toetsenbordnavigatie.
In deze setup combineer ik:
-
Een automation die snapshots maakt bij beweging aan de voordeur.
-
Twee helpers om datum en index bij te houden.
-
Een custom HTML-pagina die alle snapshots per dag toont in een mooie gallery.
-
Een aparte view op mijn tablet-dashboard met een iframe naar die gallery.
Wat je nodig hebt
-
Home Assistant (met toegang tot
configuration.yamlen het Automations-scherm). -
Een werkende voordeurcamera als
camera-entiteit (bijvoorbeeld Reolink). -
Toegang tot de
/config/www-map (via Samba, Studio Code Server, SSH, …). -
Een dashboard (bijv. tablet-dashboard) waar je een
iframeof aparte view kunt toevoegen.
Ik ga er in de voorbeelden vanuit dat je camera camera.voordeur_fluent heet. Pas dit aan naar de naam van jouw camera.
Stap 1 – Map /www/visitors aanmaken
Ga in Home Assistant naar je configuratiemap en maak (als die nog niet bestaat) de volgende map aan:
/config/www/visitors
Alles wat straks in /config/www/visitors terechtkomt, is via de browser bereikbaar als:
/local/visitors/...
Daar gaat onze gallery straks mee werken.
Stap 2 – Helpers voor datum en index
We gebruiken twee helpers:
-
input_text.bezoekerslog_datum: laatste datum waarop een snapshot is gemaakt. -
input_number.bezoekerslog_index: oplopend nummer (slot) voor die dag.
Maak ze aan via:
Instellingen → Apparaten & Diensten → Helpers → + Helper
2.1 – Tekst-helper voor datum
-
Type: Tekst
-
Naam: Bezoekerslog datum
-
Entiteit-id:
input_text.bezoekerslog_datum -
Optioneel: max. lengte bijvoorbeeld
10(voorYYYY-MM-DD)
2.2 – Nummer-helper voor index
-
Type: Nummer
-
Naam: Bezoekerslog index
-
Entiteit-id:
input_number.bezoekerslog_index -
Minimum:
0 -
Maximum: bijvoorbeeld
500(meer dan genoeg voor één dag) -
Stapgrootte:
1
Stap 3 – Automatisering: snapshot bij beweging
Nu komt de kern: een automatisering die bij beweging aan de voordeur een snapshot maakt en opslaat in de map /config/www/visitors, met een bestandsnaam als:
2025-11-16_slot_07.jpg
Maak een nieuwe automatisering:
Instellingen → Automatiseringen & Scènes → Automatiseringen → + → Lege automatisering en kies daarna Bewerken in YAML.
Plak onderstaande YAML en pas de entity_id aan naar jouw sensor/camera:
alias: Bezoekerslog - snapshot voordeur
id: bezoekerslog_snapshot_voordeur
mode: single
trigger:
platform: state
entity_id: binary_sensor.voordeur_motion
to: "on"
variables:
today: "{{ now().date() | string }}"
verhoog index; begint bij 0 en telt op
new_index: "{{ (states('input_number.bezoekerslog_index') | int(0)) + 1 }}"
filename: >
{{ today }}slot{{ '%02d' | format(new_index) }}.jpg
action:
Snapshot maken
service: camera.snapshot
data:
entity_id: camera.voordeur_fluent
filename: "/config/www/visitors/{{ filename }}"
Index bijwerken
service: input_number.set_value
target:
entity_id: input_number.bezoekerslog_index
data:
value: "{{ new_index }}"
Datum bijwerken (handig voor debugging of andere logica)
service: input_text.set_value
target:
entity_id: input_text.bezoekerslog_datum
data:
value: "{{ today }}"
Opslaan, automatiseringen herladen (of gewoon Home Assistant herstarten) en je basis is klaar. Bij elke beweging krijg je nu een nieuw bestand in /config/www/visitors.
Tip: gebruik desnoods eerst een testactie via Ontwikkelaarstools → Services → camera.snapshot om te checken of de camera en het pad werken.
Stap 4 – Optioneel: index dagelijks resetten
Je hoeft dit niet per se te doen (de gallery scant gewoon alle slot-nummers), maar ik vind het netjes om de teller elke nacht te resetten.
Maak een tweede automatisering:
alias: Bezoekerslog - index dagelijks resetten
id: bezoekerslog_reset_index
mode: single
trigger:
platform: time
at: "00:05:00"
action:
service: input_number.set_value
target:
entity_id: input_number.bezoekerslog_index
data:
value: 0
Vanaf dat moment begin je iedere dag weer bij _slot_01, _slot_02, enzovoorts.
Stap 5 – HTML-pagina bezoekers_gallery.html
Nu komt het leuke deel: de gallery. Deze HTML-pagina:
-
Zoekt alle bestanden met naam
YYYY-MM-DD_slot_XX.jpgin/local/visitors. -
Toont ze in een mooie grid, nieuwste bovenaan.
-
Laat je per dag bladeren (◀ / ▶ / Vandaag-knop).
-
Heeft een lightbox met toetsenbordnavigatie (← → Esc).
Maak in Home Assistant het bestand /config/www/bezoekers_gallery.html aan en plak onderstaande code één-op-één:
Bezoekerslogboek
:root {
color-scheme: dark;
--bg: #020617;
--accent: #38bdf8;
--accent-soft: rgba(56,189,248,0.18);
--border-soft: rgba(148,163,184,0.4);
--text-main: #e5e7eb;
--text-soft: #9ca3af;
}
* {
box-sizing: border-box;
}
body {
margin: 0;
padding: 16px;
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
Roboto, Ubuntu, "Helvetica Neue", Arial, sans-serif;
background: radial-gradient(circle at top, #0f172a 0, #020617 55%, #000 100%);
color: var(--text-main);
}
.page {
max-width: 1280px;
margin: 0 auto;
}
header {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
align-items: center;
gap: 8px;
margin-bottom: 12px;
}
.title-block {
display: flex;
flex-direction: column;
gap: 2px;
}
h1 {
margin: 0;
font-size: 1.25rem;
letter-spacing: 0.03em;
text-transform: uppercase;
}
.subtitle {
font-size: 0.8rem;
color: var(--text-soft);
}
.date-bar {
display: flex;
align-items: center;
gap: 8px;
background: linear-gradient(120deg, rgba(15,118,110,0.15), rgba(56,189,248,0.16));
border-radius: 999px;
padding: 6px 10px;
border: 1px solid var(--border-soft);
backdrop-filter: blur(6px);
box-shadow: 0 18px 40px rgba(15,23,42,0.9);
}
.date-label {
font-size: 0.8rem;
font-weight: 500;
padding: 4px 10px;
border-radius: 999px;
background: rgba(15,23,42,0.9);
border: 1px solid var(--border-soft);
display: inline-flex;
align-items: center;
gap: 6px;
white-space: nowrap;
}
.date-nav {
display: inline-flex;
gap: 4px;
margin-left: 4px;
}
.chip-btn {
border-radius: 999px;
border: 1px solid var(--border-soft);
background: rgba(15,23,42,0.9);
color: var(--text-soft);
font-size: 0.75rem;
padding: 4px 9px;
cursor: pointer;
display: inline-flex;
align-items: center;
gap: 4px;
transition: background 0.15s ease, border-color 0.15s ease,
color 0.15s ease, transform 0.1s ease;
}
.chip-btn.primary {
background: var(--accent-soft);
color: var(--accent);
border-color: rgba(56,189,248,0.7);
}
.chip-btn span.icon {
font-size: 0.9rem;
line-height: 1;
}
.chip-btn:hover {
background: rgba(15,23,42,1);
border-color: var(--accent);
color: #e0f2fe;
transform: translateY(-1px);
}
main {
margin-top: 12px;
background:
radial-gradient(circle at top left, rgba(56,189,248,0.12) 0, transparent 55%),
radial-gradient(circle at bottom right, rgba(34,197,94,0.12) 0, transparent 55%),
linear-gradient(145deg, rgba(15,23,42,0.96), rgba(15,23,42,0.98));
border-radius: 22px;
padding: 12px 12px 16px;
border: 1px solid rgba(148,163,184,0.45);
box-shadow:
0 22px 55px rgba(15,23,42,0.95),
0 0 0 1px rgba(15,23,42,0.9);
position: relative;
overflow: hidden;
}
.hint-bar {
display: flex;
justify-content: space-between;
align-items: center;
gap: 6px;
margin-bottom: 10px;
font-size: 0.78rem;
color: var(--text-soft);
}
.hint-bar span.key {
padding: 1px 6px;
border-radius: 999px;
border: 1px solid rgba(148,163,184,0.5);
background: rgba(15,23,42,0.9);
font-size: 0.7rem;
margin-right: 2px;
}
.pill {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 2px 8px;
border-radius: 999px;
border: 1px solid rgba(148,163,184,0.5);
background: rgba(15,23,42,0.85);
font-size: 0.75rem;
color: var(--text-soft);
}
.pill .dot-green {
width: 6px;
height: 6px;
border-radius: 999px;
background: #22c55e;
}
.grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
gap: 10px;
}
@media (min-width: 1200px) {
.grid {
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
}
}
.thumb {
position: relative;
border-radius: 18px;
overflow: hidden;
background: radial-gradient(circle at top, #020617 0, #020617 35%, #000 100%);
border: 1px solid rgba(30,64,175,0.9);
box-shadow:
0 18px 38px rgba(15,23,42,0.84),
inset 0 0 0 1px rgba(15,23,42,0.9);
cursor: pointer;
transform-origin: center;
transition: transform 0.12s ease, box-shadow 0.12s ease, border-color 0.12s ease;
}
.thumb:hover {
transform: translateY(-2px) scale(1.01);
box-shadow:
0 28px 55px rgba(15,23,42,0.98),
0 0 0 1px rgba(56,189,248,0.55);
border-color: rgba(56,189,248,0.9);
}
.thumb img {
width: 100%;
height: 150px;
display: block;
object-fit: cover;
}
@media (min-width: 900px) {
.thumb img {
height: 180px;
}
}
.badge {
position: absolute;
left: 8px;
top: 8px;
background: rgba(15,23,42,0.92);
border-radius: 999px;
padding: 2px 7px;
font-size: 0.7rem;
color: var(--text-soft);
border: 1px solid rgba(148,163,184,0.6);
display: inline-flex;
align-items: center;
gap: 4px;
pointer-events: none;
}
.badge .dot {
width: 6px;
height: 6px;
border-radius: 999px;
background: #22c55e;
box-shadow: 0 0 0 4px rgba(34,197,94,0.25);
}
.empty {
margin-top: 16px;
background: radial-gradient(circle at left, rgba(56,189,248,0.10) 0, transparent 55%),
linear-gradient(120deg, rgba(15,23,42,0.96), rgba(15,23,42,0.99));
border-radius: 18px;
padding: 16px 14px;
border: 1px dashed rgba(148,163,184,0.55);
color: var(--text-soft);
font-size: 0.85rem;
}
.empty strong {
color: var(--text-main);
}
/* Lightbox overlay */
.lightbox {
position: fixed;
inset: 0;
background: radial-gradient(circle at top, rgba(15,23,42,0.96) 0, rgba(0,0,0,0.98) 55%, #000 100%);
display: none;
align-items: center;
justify-content: center;
z-index: 9999;
}
.lightbox.visible {
display: flex;
}
.lightbox-inner {
position: relative;
max-width: 96vw;
max-height: 94vh;
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
}
.lightbox-img-wrap {
position: relative;
max-width: 96vw;
max-height: 86vh;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
border-radius: 20px;
border: 1px solid rgba(148,163,184,0.7);
box-shadow:
0 45px 80px rgba(0,0,0,0.96),
0 0 0 1px rgba(15,23,42,1);
background: #000;
}
.lightbox-img-wrap img {
display: block;
max-width: 100%;
max-height: 100%;
object-fit: contain;
}
.lightbox-close {
position: absolute;
top: 10px;
right: 12px;
width: 30px;
height: 30px;
border-radius: 999px;
border: 1px solid rgba(148,163,184,0.8);
background: rgba(15,23,42,0.9);
color: var(--text-main);
display: inline-flex;
align-items: center;
justify-content: center;
font-size: 1rem;
cursor: pointer;
box-shadow: 0 10px 25px rgba(0,0,0,0.8);
transition: background 0.12s ease, transform 0.1s ease, border-color 0.12s ease;
}
.lightbox-close:hover {
background: rgba(30,64,175,0.95);
border-color: rgba(129,140,248,0.95);
transform: translateY(-1px);
}
.lightbox-nav {
position: absolute;
top: 50%;
transform: translateY(-50%);
width: 40px;
height: 40px;
border-radius: 999px;
border: 1px solid rgba(148,163,184,0.8);
background: radial-gradient(circle at center, rgba(15,23,42,0.98), rgba(15,23,42,0.92));
color: var(--text-main);
display: inline-flex;
align-items: center;
justify-content: center;
font-size: 1.35rem;
cursor: pointer;
box-shadow: 0 16px 40px rgba(0,0,0,0.9);
transition: background 0.12s ease, transform 0.1s ease, border-color 0.12s ease, box-shadow 0.12s ease;
}
.lightbox-nav.prev {
left: -54px;
}
.lightbox-nav.next {
right: -54px;
}
@media (max-width: 900px) {
.lightbox-nav.prev {
left: 8px;
}
.lightbox-nav.next {
right: 8px;
}
}
.lightbox-nav:hover {
background: rgba(30,64,175,0.98);
border-color: rgba(129,140,248,1);
box-shadow: 0 22px 55px rgba(15,23,42,0.95);
transform: translateY(-50%) translateY(-1px);
}
.lightbox-meta {
font-size: 0.78rem;
color: var(--text-soft);
background: rgba(15,23,42,0.9);
border-radius: 999px;
padding: 4px 10px;
border: 1px solid rgba(148,163,184,0.7);
display: inline-flex;
align-items: center;
gap: 8px;
}
.lightbox-meta span.tag {
padding: 2px 7px;
border-radius: 999px;
border: 1px solid rgba(148,163,184,0.7);
background: rgba(15,23,42,0.95);
color: var(--text-main);
font-size: 0.72rem;
}
# Bezoekerslogboek
Overzicht van snapshots bij de voordeur, gesorteerd op **werkelijke bestandsdatum (nieuw → oud)**.
📅
◀Vorige
●Vandaag
▶Volgende
<main>
<div class="hint-bar">
<div>
<span class="key">Klik</span> voor vergroting ·
gebruik <span class="key">←</span><span class="key">→</span> om te bladeren ·
<span class="key">Esc</span> om af te sluiten
</div>
<div class="pill">
<span class="dot-green"></span>
<span>Nieuwste snapshots staan bovenaan</span>
</div>
</div>
<div id="emptyState" class="empty" style="display:none">
<strong>Geen snapshots voor deze dag</strong><br>
Er zijn geen bezoekersfoto's gevonden voor deze datum.<br>
Tip: gebruik ◀ / ▶ om naar een andere dag te gaan.
</div>
<div id="grid" class="grid"></div>
</main>
✕
‹
![Bezoeker snapshot]()
›
—
—
// ===== CONFIG =====
const BASE_PATH = "/local/visitors"; // map met snapshots
const MAX_SLOTS = 200; // max. aantal slots per dag
let currentDate = new Date();
let currentList = []; // {slot, url, base, ts}
let currentIndex = 0;
function pad(n) {
return n.toString().padStart(2, "0");
}
function formatDateISO(d) {
return d.getFullYear() + "-" + pad(d.getMonth() + 1) + "-" + pad(d.getDate());
}
function formatDateHuman(d) {
const dagen = ;
const maanden = ;
return `${dagen} ${d.getDate()} ${maanden} ${d.getFullYear()}`;
}
function sameDay(a, b) {
return a.getFullYear() === b.getFullYear() &&
a.getMonth() === b.getMonth() &&
a.getDate() === b.getDate();
}
function setDateLabel() {
const label = document.getElementById("dateLabel");
const today = new Date();
const iso = formatDateISO(currentDate);
let prefix = "";
if (sameDay(currentDate, today)) {
prefix = "Vandaag · ";
} else {
const yesterday = new Date();
yesterday.setDate(today.getDate() - 1);
if (sameDay(currentDate, yesterday)) {
prefix = "Gisteren · ";
}
}
label.textContent = prefix + iso + " (" + formatDateHuman(currentDate) + ")";
}
function clearGrid() {
document.getElementById("grid").innerHTML = "";
}
function showEmpty(visible) {
document.getElementById("emptyState").style.display = visible ? "block" : "none";
}
function renderGrid(list, iso) {
const grid = document.getElementById("grid");
grid.innerHTML = "";
currentList = list.slice(); // kopie
list.forEach((item, idx) => {
const thumb = document.createElement("div");
thumb.className = "thumb";
thumb.dataset.index = String(idx);
const img = document.createElement("img");
img.src = item.url;
img.alt = `Bezoeker snapshot ${iso} slot ${item.slot}`;
const badge = document.createElement("div");
badge.className = "badge";
const dot = document.createElement("span");
dot.className = "dot";
const txt = document.createElement("span");
const timeStr = item.ts
? new Date(item.ts).toLocaleTimeString("nl-NL", { hour: "2-digit", minute: "2-digit" })
: `slot ${item.slot}`;
txt.textContent = timeStr;
badge.appendChild(dot);
badge.appendChild(txt);
thumb.appendChild(img);
thumb.appendChild(badge);
thumb.addEventListener("click", () => {
currentIndex = idx;
openLightboxCurrent();
});
grid.appendChild(thumb);
});
}
async function enrichWithTimestamps(found) {
// Haal Last-Modified per bestand op en sorteer daarna
const enriched = await Promise.all(
found.map(async (item) => {
try {
const resp = await fetch(item.base, { method: "HEAD" });
const lm = resp.headers.get("Last-Modified");
if (lm) {
item.ts = new Date(lm).getTime();
} else {
item.ts = 0;
}
} catch (e) {
item.ts = 0;
}
return item;
})
);
// Sorteer: eerst op timestamp desc, dan op slot desc als fallback
enriched.sort((a, b) => {
if (b.ts !== a.ts) return b.ts - a.ts;
return (b.slot || 0) - (a.slot || 0);
});
return enriched;
}
function loadDay() {
clearGrid();
showEmpty(false);
setDateLabel();
currentList = [];
const iso = formatDateISO(currentDate);
const candidates = [];
for (let slot = 0; slot {
pending--;
if (pending === 0) {
if (!found.length) {
showEmpty(true);
return;
}
// Eerst timestamps ophalen, dan sorteren en renderen
enrichWithTimestamps(found)
.then(sorted => {
renderGrid(sorted, iso);
})
.catch(() => {
// Fallback: als er iets misgaat, gewoon op slot desc
found.sort((a, b) => (b.slot || 0) - (a.slot || 0));
renderGrid(found, iso);
});
}
};
candidates.forEach(c => {
const img = new Image();
img.onload = () => {
found.push(c);
done();
};
img.onerror = () => {
done();
};
img.src = c.url;
});
}
// ===== Lightbox =====
function openLightboxCurrent() {
if (!currentList.length) return;
const item = currentList;
const lightbox = document.getElementById("lightbox");
const lbImg = document.getElementById("lightboxImg");
const metaDate = document.getElementById("metaDate");
const metaInfo = document.getElementById("metaInfo");
lbImg.src = item.url;
const cleanSrc = item.base || item.url.split("?");
const file = cleanSrc.split("/").pop() || "";
const parts = file.split("_slot_");
if (parts.length === 2) {
const d = parts;
const slotPart = parts.split(".");
metaDate.textContent = d;
if (item.ts) {
const t = new Date(item.ts).toLocaleTimeString("nl-NL", {
hour: "2-digit",
minute: "2-digit",
second: "2-digit"
});
metaInfo.textContent = `Snapshot ${slotPart} · ${t}`;
} else {
metaInfo.textContent = `Snapshot ${slotPart}`;
}
} else {
metaDate.textContent = cleanSrc;
metaInfo.textContent = "";
}
lightbox.classList.add("visible");
lightbox.setAttribute("aria-hidden", "false");
}
function closeLightbox() {
const lightbox = document.getElementById("lightbox");
const lbImg = document.getElementById("lightboxImg");
lbImg.src = "";
lightbox.classList.remove("visible");
lightbox.setAttribute("aria-hidden", "true");
}
function showRelativeImage(delta) {
if (!currentList.length) return;
const len = currentList.length;
currentIndex = (currentIndex + delta + len) % len;
openLightboxCurrent();
}
// ===== Events =====
document.getElementById("prevDayBtn").addEventListener("click", () => {
currentDate.setDate(currentDate.getDate() - 1);
loadDay();
});
document.getElementById("nextDayBtn").addEventListener("click", () => {
currentDate.setDate(currentDate.getDate() + 1);
loadDay();
});
document.getElementById("todayBtn").addEventListener("click", () => {
const now = new Date();
currentDate = new Date(now.getFullYear(), now.getMonth(), now.getDate());
loadDay();
});
document.getElementById("closeLightboxBtn").addEventListener("click", closeLightbox);
document.getElementById("prevImgBtn").addEventListener("click", () => showRelativeImage(-1));
document.getElementById("nextImgBtn").addEventListener("click", () => showRelativeImage(1));
document.getElementById("lightbox").addEventListener("click", (e) => {
if (e.target.id === "lightbox") {
closeLightbox();
}
});
document.addEventListener("keydown", (e) => {
const lightbox = document.getElementById("lightbox");
if (!lightbox.classList.contains("visible")) return;
if (e.key === "Escape") {
closeLightbox();
} else if (e.key === "ArrowLeft") {
showRelativeImage(-1);
} else if (e.key === "ArrowRight") {
showRelativeImage(1);
}
});
// ===== Init =====
(function init() {
const now = new Date();
currentDate = new Date(now.getFullYear(), now.getMonth(), now.getDate());
loadDay();
})();
Opslaan, en je gallery-pagina is klaar.
Stap 6 – Bezoekerslogboek op je tablet-dashboard
Nu moeten we de HTML-pagina nog tonen in Home Assistant, bijvoorbeeld als aparte view in je tablet-dashboard. Onderstaande YAML is een simpel voorbeeld van een tweede view met alleen een iframe naar de gallery:
views:
title: Tablet
path: tablet-v2
icon: mdi:tablet-dashboard
... jouw bestaande dashboard-config ...
title: Bezoekerslog
path: bezoekerslog
icon: mdi:cctv
panel: true
cards:
type: iframe
url: /local/bezoekers_gallery.html?v=1
aspect_ratio: 100%
Belangrijk:
-
panel: truezorgt ervoor dat de iframe fullscreen in de view staat. -
De
?v=1achter de URL kun je verhogen (2, 3, 4, …) als je last hebt van cache in je browser. -
Vanaf je hoofddashboard kun je een button maken met
tap_action: navigatenaar/tablet-v2/1(of de juiste route) om snel naar het bezoekerslogboek te gaan.
Stap 7 – Testen
-
Controleer in de map
/config/www/visitorsof er bestanden verschijnen zoals2025-11-16_slot_01.jpgals je naar de voordeur loopt. -
Open de gallery in je browser:
https://jouw-ha-url/local/bezoekers_gallery.html. -
Blader met de knoppen Vorige / Vandaag / Volgende door de dagen.
-
Klik op een foto om de lightbox te openen; gebruik je toetsenbord:
-
← / → om door de snapshots te bladeren;
-
Esc om de lightbox te sluiten.
-
-
Controleer of dezelfde gallery ook goed in je tablet-dashboard-view wordt getoond.
Veelvoorkomende issues (& oplossingen)
-
Geen foto’s in de gallery Controleer of er wel bestanden in
/config/www/visitorsstaan én of de bestandsnamen het patroonYYYY-MM-DD_slot_XX.jpgvolgen. -
Snapshots werken, maar je ziet oude volgorde De HTML sorteert op bestandsdatum (Last-Modified). Als je bestanden handmatig kopieert, kan dat verwarrend zijn. Laat het systeem ze zelf aanmaken.
-
Cache-problemen Als je wijzigingen aan de HTML hebt gedaan maar niets ziet:
Verhoog de
?v=1parameter in de iframe-URL naar?v=2etc.- Force-refresh in je browser (Ctrl+F5 / Cmd+Shift+R).
Te veel snapshots per dag
Verklein eventueel MAX_SLOTS in de HTML (of beperk je voordeur-automation met cooldown / conditions). Standaard staat hij op 200.
Producten die ik gebruik
Voordeurcamera (Reolink) – Bewaking en snapshots voor het bezoekerslogboek.
Android-tablet – Als vast Home Assistant-dashboard aan de muur.
Wallmount – Om de tablet netjes weg te werken.
Home Assistant – Het hart van de automatisering en de gallery.
Slot
Met dit bezoekerslogboek heb ik mijn voordeurcamera écht geïntegreerd in mijn smart home:
-
Alle bezoekers netjes onder elkaar, per dag gegroepeerd.
-
Een stijlvolle gallery op mijn tablet-dashboard.
-
Bladeren door de tijd (en door de foto’s) zonder apps te hoeven openen.
Kopieer de YAML, plak de HTML in /config/www/bezoekers_gallery.html, pas je entiteiten aan en je hebt je eigen bezoekerslogboek draaien. En het mooie: de basis is nu gelegd voor een vervolg – bijvoorbeeld met gezichtsherkenning via een Google Coral USB, zodat je straks niet alleen ziet dát er iemand voor de deur stond, maar ook wie. 😄