From 0aa70d364d6334dd56ae837158d13d7dbd7ce967 Mon Sep 17 00:00:00 2001 From: bes-r Date: Sat, 6 Jun 2026 21:34:29 +0200 Subject: [PATCH] gen3-gen4/1PM(mini)/detached-script.js toevoegen --- gen3-gen4/1PM(mini)/detached-script.js | 238 +++++++++++++++++++++++++ 1 file changed, 238 insertions(+) create mode 100644 gen3-gen4/1PM(mini)/detached-script.js diff --git a/gen3-gen4/1PM(mini)/detached-script.js b/gen3-gen4/1PM(mini)/detached-script.js new file mode 100644 index 0000000..6fcd6d2 --- /dev/null +++ b/gen3-gen4/1PM(mini)/detached-script.js @@ -0,0 +1,238 @@ +// ============================================================================= +// Shelly 1PM Detached Script v3.5 +// ============================================================================= +// Single-channel variant. Geen crosstalk-beveiliging nodig. +// Geschikt voor: Gen4 1PM, Gen4 1PM Mini, Gen3 1PM +// +// Telt alleen btn_down events om valse meldingen van Gira Systeem 55 +// (of andere wipknoppen) te verminderen. +// ============================================================================= +var CONFIG = { + channels: [ + { inputId: 0, outputId: 0 } + ], + SETTLE_MS: 300, + COUNT_SIGNAL: "btn_down", + LONG_ENABLED: true, + LONG_MIN_MS: 800, + LONG_MAX_MS: 1800, + EMERGENCY_COUNT: 5, + EMERGENCY_WINDOW_MS: 2500, + names: { + single: "single", + double: "double", + triple: "triple", + long: "long", + emergency: "emergency" + } +}; + +var deviceInfo = Shelly.getDeviceInfo(); +var deviceName = (deviceInfo.name && deviceInfo.name !== "") + ? deviceInfo.name + : deviceInfo.model + " " + deviceInfo.id; + +for (var ci = 0; ci < CONFIG.channels.length; ci++) { + var inputCfg = Shelly.getComponentConfig("input:" + CONFIG.channels[ci].inputId); + CONFIG.channels[ci].name = (inputCfg && inputCfg.name && inputCfg.name !== "") + ? inputCfg.name + : "Channel " + (ci + 1); +} + +var chState = []; +for (var si = 0; si < CONFIG.channels.length; si++) { + chState.push({ + stateChanges: 0, + settleTimer: null, + longTimer: null, + settling: false, + waitingForLong: false, + singleTime: 0, + lastBurstEnd: 0, + emergencyCount: 0, + emergencyTimer: null, + emergencyLocked: false + }); +} + +function fireEvent(chIndex, logicalType, toggles) { + var ch = CONFIG.channels[chIndex]; + var eventType = CONFIG.names[logicalType] || logicalType; + print(">>> " + ch.name + ": " + eventType + + " (" + JSON.stringify(toggles) + " toggles)"); + Shelly.emitEvent("toggle_event", { + input: ch.inputId, + input_name: ch.name, + output: ch.outputId, + device: deviceName, + type: eventType, + logical_type: logicalType, + toggles: toggles + }); + if (logicalType === "single") { + var w = Shelly.getComponentStatus("wifi"); + if (w === null || w.sta_ip === null || w.sta_ip === "") { + print("[Offline] Local toggle: " + ch.name); + Shelly.call("Switch.Toggle", { id: ch.outputId }); + } + } +} + +function resetEmergency(chIndex) { + var s = chState[chIndex]; + s.emergencyCount = 0; + s.emergencyLocked = false; + if (s.emergencyTimer !== null) { + Timer.clear(s.emergencyTimer); + s.emergencyTimer = null; + } +} + +function registerEmergency(chIndex, toggles) { + var s = chState[chIndex]; + if (toggles < 1) return false; + if (s.emergencyLocked) return true; + if (s.emergencyCount === 0) { + s.emergencyTimer = Timer.set(CONFIG.EMERGENCY_WINDOW_MS, false, function() { + print("[Emergency] Timeout " + CONFIG.channels[chIndex].name + + " after " + JSON.stringify(s.emergencyCount) + " toggles"); + resetEmergency(chIndex); + }); + } + s.emergencyCount += toggles; + print("[Emergency] " + CONFIG.channels[chIndex].name + + ": " + JSON.stringify(s.emergencyCount) + + "/" + JSON.stringify(CONFIG.EMERGENCY_COUNT)); + if (s.emergencyCount >= CONFIG.EMERGENCY_COUNT) { + s.emergencyLocked = true; + if (s.emergencyTimer !== null) { + Timer.clear(s.emergencyTimer); + s.emergencyTimer = null; + } + fireEvent(chIndex, "emergency", s.emergencyCount); + Timer.set(1000, false, function() { + resetEmergency(chIndex); + }); + return true; + } + return false; +} + +function emitForToggles(chIndex, toggles) { + if (registerEmergency(chIndex, toggles)) return; + var type = "single"; + if (toggles >= 3) type = "triple"; + else if (toggles >= 2) type = "double"; + fireEvent(chIndex, type, toggles); +} + +function onBurstEnd(chIndex) { + var s = chState[chIndex]; + s.settling = false; + s.settleTimer = null; + s.lastBurstEnd = Date.now(); + var changes = s.stateChanges; + s.stateChanges = 0; + var toggles = changes; + if (toggles < 1) toggles = 1; + print("[Burst] " + CONFIG.channels[chIndex].name + + ": " + JSON.stringify(changes) + " counted events -> " + + JSON.stringify(toggles) + " toggles"); + if (s.waitingForLong) { + var elapsed = Date.now() - s.singleTime; + s.waitingForLong = false; + if (s.longTimer !== null) { + Timer.clear(s.longTimer); + s.longTimer = null; + } + if (toggles === 1 && + elapsed >= CONFIG.LONG_MIN_MS && + elapsed <= CONFIG.LONG_MAX_MS) { + fireEvent(chIndex, "long", 1); + return; + } + fireEvent(chIndex, "single", 1); + if (toggles > 0) { + emitForToggles(chIndex, toggles); + } + return; + } + if (toggles === 1 && CONFIG.LONG_ENABLED) { + s.waitingForLong = true; + s.singleTime = Date.now(); + print("[Long] Waiting for return toggle " + CONFIG.channels[chIndex].name); + s.longTimer = Timer.set(CONFIG.LONG_MAX_MS, false, function() { + s.waitingForLong = false; + s.longTimer = null; + print("[Long] Timeout -> single"); + emitForToggles(chIndex, 1); + }); + return; + } + emitForToggles(chIndex, toggles); +} + +Shelly.addEventHandler(function(event) { + if (event.component === undefined) return; + if (event.info === undefined) return; + var chIndex = -1; + for (var i = 0; i < CONFIG.channels.length; i++) { + if (event.component === ("input:" + CONFIG.channels[i].inputId)) { + chIndex = i; + break; + } + } + if (chIndex === -1) return; + var signal = null; + if (event.info.state !== undefined) { + signal = event.info.state; + } else if (event.info.event !== undefined) { + signal = event.info.event; + } else { + return; + } + var s = chState[chIndex]; + // Geen crosstalk-logica nodig voor single-channel apparaten. + if (signal === CONFIG.COUNT_SIGNAL || event.info.state !== undefined) { + s.stateChanges += 1; + s.settling = true; + print("[State] " + CONFIG.channels[chIndex].name + + " = " + JSON.stringify(signal) + + " (#" + JSON.stringify(s.stateChanges) + ")"); + if (s.settleTimer !== null) { + Timer.clear(s.settleTimer); + } + s.settleTimer = Timer.set(CONFIG.SETTLE_MS, false, function() { + onBurstEnd(chIndex); + }); + return; + } + if (signal === "btn_up") { + print("[Ignored] " + CONFIG.channels[chIndex].name + + " = " + JSON.stringify(signal)); + return; + } + if (signal === "single_push" || + signal === "double_push" || + signal === "triple_push" || + signal === "long_push") { + print("[Native] " + CONFIG.channels[chIndex].name + + " = " + JSON.stringify(signal)); + } +}); + +print("=== Shelly 1PM Detached v3.5 ==="); +print("Geschikt voor: Gen4 1PM, Gen4 1PM Mini, Gen3 1PM"); +print("Device: " + deviceName); +print("Timing: settle=" + JSON.stringify(CONFIG.SETTLE_MS) + "ms"); +print("Counting only: " + CONFIG.COUNT_SIGNAL); +print("Long: " + (CONFIG.LONG_ENABLED ? "enabled" : "disabled")); +print("Emergency: " + CONFIG.EMERGENCY_COUNT + " toggles / " + + CONFIG.EMERGENCY_WINDOW_MS + "ms"); +for (var pi = 0; pi < CONFIG.channels.length; pi++) { + var ch = CONFIG.channels[pi]; + print(" " + ch.name + + " input:" + ch.inputId + + " -> output:" + ch.outputId); +} +print("[Script] Active"); \ No newline at end of file