Skip to content

Commit f78c546

Browse files
committed
Update / Modification from the production live system: 2026-03-04 23:50
1 parent 1812378 commit f78c546

1 file changed

Lines changed: 212 additions & 0 deletions

File tree

Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
/*******************************************************************************
2+
* Skriptname: Klingelsignal & Sturm Protection
3+
* Beschreibung: Verarbeitet das Klingelsignal (HmIP), steuert den Türsummer
4+
* bei gewährtem Zutritt und gibt eine Alexa-Sprachmeldung aus.
5+
* Inklusive "Sturm Protection" (Cooldown) gegen Dauer-Klingeln.
6+
* Autor: Sanweb
7+
* * -----------------------------------------------------------------------------
8+
* Versionshistorie:
9+
* v1.0.0 | 04.03.2026 | Initiale Version
10+
*******************************************************************************/
11+
12+
// ==============================================================================
13+
// KONFIGURATION: DATENPUNKTE BITTE ANPASSEN
14+
// ==============================================================================
15+
(async () => {
16+
17+
// Der von dir genannte Datenpunkt des HmIP Klingelsensors
18+
// (Hinweis: '1.STATE' repräsentiert Kanal 1 und den Datenpunkt STATE)
19+
const DP_KLINGEL = 'hm-rpc.0.0026E0C998D1F2.1.STATE';
20+
21+
// Datenpunkt der Zutrittskontrolle (Boolean erwartet: true = gewährt, false = verweigert)
22+
// Falls du eine Werteliste nutzt, muss die Abfrage weiter unten angepasst werden.
23+
const DP_ZUTRITTSKONTROLLE = '0_userdata.0.Status.Haustuer.Zutrittskontrolle_Haustuer';
24+
25+
// Datenpunkt zum Speichern des letzten Klingelzeitpunkts (Typ: Number/Zahl)
26+
const DP_LAST_RING = '0_userdata.0.Status.Klingel_LastRingTime';
27+
28+
// Datenpunkt für den HmIP-PCBS Haustürsummer (meist Kanal 3 oder 4, .STATE)
29+
const DP_TUERSUMMER = 'hm-rpc.0.00045A49A87CFF.3.STATE';
30+
31+
// Datenpunkt für die Alexa Sprachausgabe (Gruppe Überall)
32+
const DP_ALEXA_SPEAK = 'alexa2.0.Echo-Devices.e4a56dc3ec9a497c9209c05d2c29a09d.Commands.speak';
33+
34+
// ==============================================================================
35+
// KONFIGURATION: VARIABLEN UND BEFEHLE
36+
// ==============================================================================
37+
const ALEXA_MESSAGE = 'Ding Dong, es hat an der Haustür geklingelt!';
38+
39+
// Konfiguration der Zeiten
40+
const ALEXA_START_ZEIT = '11:00'; // Ab wann darf Alexa sprechen? (Format HH:MM)
41+
const ALEXA_ENDE_ZEIT = '22:00'; // Bis wann darf Alexa sprechen? (Format HH:MM)
42+
const TUERSUMMER_DAUER_MS = 2000; // Wie lange zieht der Türsummer an? (in Millisekunden)
43+
const STURM_PROTECTION_MS = 33000; // Wie lange ist die Klingel nach Betätigung gesperrt? (in Millisekunden)
44+
45+
const DEBUG = false; // Auf true setzen für Entwicklung / detailliertes Logging
46+
47+
// Interne Variablen für Timer und den Klingelschutz (Sturm Protection)
48+
let lastRingTime = 0; // Speichert den Zeitstempel des letzten Klingelns (verhindert Race Conditions)
49+
let timerTuersummer = null;
50+
51+
// ==============================================================================
52+
// HILFSFUNKTIONEN
53+
// ==============================================================================
54+
55+
// Debug-Logging
56+
function logDebug(msg) {
57+
if (DEBUG) log(msg, 'debug');
58+
}
59+
60+
// Funktion zum Prüfen ob die aktuelle Zeit im konfigurierten Fenster liegt
61+
function isTimeInRange(startTime, endTime) {
62+
const now = new Date();
63+
const startParts = startTime.split(':');
64+
const endParts = endTime.split(':');
65+
66+
const startMins = parseInt(startParts[0], 10) * 60 + parseInt(startParts[1], 10);
67+
const endMins = parseInt(endParts[0], 10) * 60 + parseInt(endParts[1], 10);
68+
const currentMins = now.getHours() * 60 + now.getMinutes();
69+
70+
return currentMins >= startMins && currentMins < endMins;
71+
}
72+
73+
// Prüft synchron und sicher, ob ein Datenpunkt im ioBroker existiert
74+
function stateExists(id) {
75+
return typeof getObject(id) !== 'undefined';
76+
}
77+
78+
// Sicheres Setzen von States mit Prüfung und Logging sowie Callback-Fehlerbehandlung
79+
function safeSetState(id, val) {
80+
if (stateExists(id)) {
81+
log(`Setze Datenpunkt "${id}" auf Wert: ${val}`, 'debug');
82+
// Hinweis zum ack-Flag:
83+
// Andere States hier nutzen ack=false (Steuerbefehle an Geräte).
84+
// DP_LAST_RING nutzt im Hauptprogramm ack=true (reiner Status, keine Steuerung).
85+
setState(id, val, false, (err) => {
86+
if (err) log(`Fehler beim Setzen von ${id}: ${err}`, 'error');
87+
});
88+
} else {
89+
log(`Fehler: Datenpunkt "${id}" existiert nicht. Aktion abgebrochen.`, 'warn');
90+
}
91+
}
92+
93+
// ==============================================================================
94+
// INITIALISIERUNG
95+
// ==============================================================================
96+
97+
// Plausibilitätsprüfung der konfigurierten Zeiten
98+
if (!/^([01]\d|2[0-3]):([0-5]\d)$/.test(ALEXA_START_ZEIT) || !/^([01]\d|2[0-3]):([0-5]\d)$/.test(ALEXA_ENDE_ZEIT)) {
99+
log('WARNUNG: ALEXA_START_ZEIT oder ALEXA_ENDE_ZEIT haben ein ungültiges Format! Erwartet wird HH:MM', 'warn');
100+
} else {
101+
// Zusätzlich prüfen, ob Startzeit vor Endzeit liegt
102+
const startParts = ALEXA_START_ZEIT.split(':');
103+
const endParts = ALEXA_ENDE_ZEIT.split(':');
104+
const startMins = parseInt(startParts[0], 10) * 60 + parseInt(startParts[1], 10);
105+
const endMins = parseInt(endParts[0], 10) * 60 + parseInt(endParts[1], 10);
106+
if (startMins >= endMins) {
107+
log('WARNUNG: ALEXA_START_ZEIT liegt nach oder auf ALEXA_ENDE_ZEIT!', 'warn');
108+
}
109+
}
110+
111+
// Wir prüfen nur, ob der Zutrittskontroll-Datenpunkt existiert.
112+
// Manuelles Anlegen im ioBroker-Objektbaum wird empfohlen, um asynchrone Fehler durch createState zu vermeiden.
113+
if (!stateExists(DP_ZUTRITTSKONTROLLE)) {
114+
log(`WICHTIG: Der Datenpunkt ${DP_ZUTRITTSKONTROLLE} existiert nicht. Bitte lege ihn manuell als Logikwert (boolean) an!`, 'error');
115+
}
116+
117+
// Letzten Klingel-Zeitpunkt laden, falls der Datenpunkt existiert
118+
if (stateExists(DP_LAST_RING)) {
119+
const savedTime = getState(DP_LAST_RING).val;
120+
if (typeof savedTime === 'number' && !isNaN(savedTime)) {
121+
lastRingTime = savedTime;
122+
logDebug(`Letzter Klingelzeitpunkt aus Datenpunkt geladen: ${lastRingTime}`);
123+
} else {
124+
log('WARNUNG: Gespeicherter Klingelzeitpunkt ist ungültig, verwende 0', 'warn');
125+
lastRingTime = 0;
126+
}
127+
} else {
128+
log(`HINWEIS: Der Datenpunkt ${DP_LAST_RING} existiert nicht. Lege ihn als Zahl (number) an, um die Sturm-Protection über Skript-Neustarts hinweg zu erhalten.`, 'info');
129+
}
130+
131+
// ==============================================================================
132+
// HAUPTPROGRAMM (TRIGGER)
133+
// ==============================================================================
134+
135+
// Trigger auf Aktualisierung des Klingelsensors (wenn Wert 'true' ist)
136+
on({id: DP_KLINGEL, val: true, ack: true}, function (obj) {
137+
138+
const now = Date.now();
139+
140+
// 1. KLINGELSCHUTZ PRÜFEN (Timestamp-basierter Cooldown)
141+
if ((now - lastRingTime) < STURM_PROTECTION_MS) {
142+
const remaining = Math.round((STURM_PROTECTION_MS - (now - lastRingTime)) / 1000);
143+
log(`Klingel betätigt, aber Sturm Protection ist noch aktiv (${remaining}s verbleibend). Aktion ignoriert.`, 'info');
144+
return;
145+
}
146+
147+
// Klingelschutz aktivieren (Zeitstempel setzen)
148+
lastRingTime = now;
149+
if (stateExists(DP_LAST_RING)) {
150+
// Hinweis zum ack-Flag: ack=true, da wir hier nur einen Status im System protokollieren und nichts steuern
151+
setState(DP_LAST_RING, lastRingTime, true);
152+
}
153+
log('Klingelsignal erkannt. Ablauf startet, Sturm Protection aktiviert.', 'info');
154+
155+
// Aktuellen Status der Zutrittskontrolle abrufen (Explizite Boolean-Prüfung)
156+
let zutrittGewaehrt = false;
157+
if (stateExists(DP_ZUTRITTSKONTROLLE)) {
158+
zutrittGewaehrt = (getState(DP_ZUTRITTSKONTROLLE).val === true);
159+
}
160+
161+
if (!zutrittGewaehrt) {
162+
// ----------------------------------------------------------------------
163+
// BEDINGUNG: Zutritt verweigert (Normale Klingelfunktion)
164+
// ----------------------------------------------------------------------
165+
log('Zutritt verweigert. Führe normalen Klingelablauf aus.', 'info');
166+
167+
// Uhrzeit abfragen (mit neuer HH:MM Prüfung)
168+
if (isTimeInRange(ALEXA_START_ZEIT, ALEXA_ENDE_ZEIT)) {
169+
// Alexa Sprachausgabe
170+
safeSetState(DP_ALEXA_SPEAK, ALEXA_MESSAGE);
171+
} else {
172+
log(`Uhrzeit außerhalb ${ALEXA_START_ZEIT}-${ALEXA_ENDE_ZEIT} Uhr: Alexa bleibt stumm.`, 'info');
173+
}
174+
175+
} else {
176+
// ----------------------------------------------------------------------
177+
// BEDINGUNG: Zutritt gewährt (Haustürsummer auslösen)
178+
// ----------------------------------------------------------------------
179+
log('Zutritt gewährt. Betätige Türsummer.', 'info');
180+
181+
// Haustürsummer sofort Ein
182+
safeSetState(DP_TUERSUMMER, true);
183+
184+
// Haustürsummer nach definierter Zeit Aus
185+
if (timerTuersummer) clearTimeout(timerTuersummer);
186+
logDebug('Timer für Türsummer gestartet');
187+
timerTuersummer = setTimeout(function() {
188+
safeSetState(DP_TUERSUMMER, false);
189+
}, TUERSUMMER_DAUER_MS);
190+
191+
// Zutrittskontrolle sofort wieder auf "verweigert" zurücksetzen
192+
safeSetState(DP_ZUTRITTSKONTROLLE, false);
193+
}
194+
});
195+
196+
// ==============================================================================
197+
// SKRIPT STOPP BEHANDLUNG
198+
// ==============================================================================
199+
onStop(function () {
200+
log('Skript wird gestoppt. Bereinige Timer und setze Türsummer zurück...', 'info');
201+
if (timerTuersummer) {
202+
clearTimeout(timerTuersummer);
203+
timerTuersummer = null;
204+
// Sicherheitshalber den Summer ausschalten, falls er gerade an war
205+
if (stateExists(DP_TUERSUMMER)) {
206+
setState(DP_TUERSUMMER, false);
207+
log('Türsummer wegen Skript-Stopp sicherheitshalber ausgeschaltet.', 'info');
208+
}
209+
}
210+
});
211+
212+
})();

0 commit comments

Comments
 (0)