Compare commits
No commits in common. "main" and "dev" have entirely different histories.
3 changed files with 60 additions and 431 deletions
|
|
@ -1,25 +1,28 @@
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
// Shelly 2PM Detached Script v3.5
|
// Shelly 2PM Detached Script v3.5
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
// Telt alleen btn_down events om valse meldingen van Gira Systeem 55
|
// More stable version: counts only btn_down events.
|
||||||
// (of andere wipknoppen) te verminderen.
|
// This reduces ghost triggers caused by extra btn_up / bounce events.
|
||||||
// Crosstalk-beveiliging voorkomt dat één knop van een dubbele schakelaar
|
|
||||||
// de andere knop triggert.
|
|
||||||
// Geschikt voor: Gen4 2PM, Gen3 2PM
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|
||||||
var CONFIG = {
|
var CONFIG = {
|
||||||
channels: [
|
channels: [
|
||||||
{ inputId: 0, outputId: 0 },
|
{ inputId: 0, outputId: 0 },
|
||||||
{ inputId: 1, outputId: 1 }
|
{ inputId: 1, outputId: 1 }
|
||||||
],
|
],
|
||||||
|
|
||||||
SETTLE_MS: 300,
|
SETTLE_MS: 300,
|
||||||
CROSSTALK_MS: 300,
|
CROSSTALK_MS: 300,
|
||||||
|
|
||||||
COUNT_SIGNAL: "btn_down",
|
COUNT_SIGNAL: "btn_down",
|
||||||
|
|
||||||
LONG_ENABLED: true,
|
LONG_ENABLED: true,
|
||||||
LONG_MIN_MS: 800,
|
LONG_MIN_MS: 800,
|
||||||
LONG_MAX_MS: 1800,
|
LONG_MAX_MS: 1800,
|
||||||
|
|
||||||
EMERGENCY_COUNT: 5,
|
EMERGENCY_COUNT: 5,
|
||||||
EMERGENCY_WINDOW_MS: 2500,
|
EMERGENCY_WINDOW_MS: 2500,
|
||||||
|
|
||||||
names: {
|
names: {
|
||||||
single: "single",
|
single: "single",
|
||||||
double: "double",
|
double: "double",
|
||||||
|
|
@ -60,8 +63,10 @@ for (var si = 0; si < CONFIG.channels.length; si++) {
|
||||||
function fireEvent(chIndex, logicalType, toggles) {
|
function fireEvent(chIndex, logicalType, toggles) {
|
||||||
var ch = CONFIG.channels[chIndex];
|
var ch = CONFIG.channels[chIndex];
|
||||||
var eventType = CONFIG.names[logicalType] || logicalType;
|
var eventType = CONFIG.names[logicalType] || logicalType;
|
||||||
|
|
||||||
print(">>> " + ch.name + ": " + eventType +
|
print(">>> " + ch.name + ": " + eventType +
|
||||||
" (" + JSON.stringify(toggles) + " toggles)");
|
" (" + JSON.stringify(toggles) + " toggles)");
|
||||||
|
|
||||||
Shelly.emitEvent("toggle_event", {
|
Shelly.emitEvent("toggle_event", {
|
||||||
input: ch.inputId,
|
input: ch.inputId,
|
||||||
input_name: ch.name,
|
input_name: ch.name,
|
||||||
|
|
@ -71,6 +76,7 @@ function fireEvent(chIndex, logicalType, toggles) {
|
||||||
logical_type: logicalType,
|
logical_type: logicalType,
|
||||||
toggles: toggles
|
toggles: toggles
|
||||||
});
|
});
|
||||||
|
|
||||||
if (logicalType === "single") {
|
if (logicalType === "single") {
|
||||||
var w = Shelly.getComponentStatus("wifi");
|
var w = Shelly.getComponentStatus("wifi");
|
||||||
if (w === null || w.sta_ip === null || w.sta_ip === "") {
|
if (w === null || w.sta_ip === null || w.sta_ip === "") {
|
||||||
|
|
@ -84,6 +90,7 @@ function resetEmergency(chIndex) {
|
||||||
var s = chState[chIndex];
|
var s = chState[chIndex];
|
||||||
s.emergencyCount = 0;
|
s.emergencyCount = 0;
|
||||||
s.emergencyLocked = false;
|
s.emergencyLocked = false;
|
||||||
|
|
||||||
if (s.emergencyTimer !== null) {
|
if (s.emergencyTimer !== null) {
|
||||||
Timer.clear(s.emergencyTimer);
|
Timer.clear(s.emergencyTimer);
|
||||||
s.emergencyTimer = null;
|
s.emergencyTimer = null;
|
||||||
|
|
@ -92,8 +99,10 @@ function resetEmergency(chIndex) {
|
||||||
|
|
||||||
function registerEmergency(chIndex, toggles) {
|
function registerEmergency(chIndex, toggles) {
|
||||||
var s = chState[chIndex];
|
var s = chState[chIndex];
|
||||||
|
|
||||||
if (toggles < 1) return false;
|
if (toggles < 1) return false;
|
||||||
if (s.emergencyLocked) return true;
|
if (s.emergencyLocked) return true;
|
||||||
|
|
||||||
if (s.emergencyCount === 0) {
|
if (s.emergencyCount === 0) {
|
||||||
s.emergencyTimer = Timer.set(CONFIG.EMERGENCY_WINDOW_MS, false, function() {
|
s.emergencyTimer = Timer.set(CONFIG.EMERGENCY_WINDOW_MS, false, function() {
|
||||||
print("[Emergency] Timeout " + CONFIG.channels[chIndex].name +
|
print("[Emergency] Timeout " + CONFIG.channels[chIndex].name +
|
||||||
|
|
@ -101,92 +110,124 @@ function registerEmergency(chIndex, toggles) {
|
||||||
resetEmergency(chIndex);
|
resetEmergency(chIndex);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
s.emergencyCount += toggles;
|
s.emergencyCount += toggles;
|
||||||
|
|
||||||
print("[Emergency] " + CONFIG.channels[chIndex].name +
|
print("[Emergency] " + CONFIG.channels[chIndex].name +
|
||||||
": " + JSON.stringify(s.emergencyCount) +
|
": " + JSON.stringify(s.emergencyCount) +
|
||||||
"/" + JSON.stringify(CONFIG.EMERGENCY_COUNT));
|
"/" + JSON.stringify(CONFIG.EMERGENCY_COUNT));
|
||||||
|
|
||||||
if (s.emergencyCount >= CONFIG.EMERGENCY_COUNT) {
|
if (s.emergencyCount >= CONFIG.EMERGENCY_COUNT) {
|
||||||
s.emergencyLocked = true;
|
s.emergencyLocked = true;
|
||||||
|
|
||||||
if (s.emergencyTimer !== null) {
|
if (s.emergencyTimer !== null) {
|
||||||
Timer.clear(s.emergencyTimer);
|
Timer.clear(s.emergencyTimer);
|
||||||
s.emergencyTimer = null;
|
s.emergencyTimer = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
fireEvent(chIndex, "emergency", s.emergencyCount);
|
fireEvent(chIndex, "emergency", s.emergencyCount);
|
||||||
|
|
||||||
Timer.set(1000, false, function() {
|
Timer.set(1000, false, function() {
|
||||||
resetEmergency(chIndex);
|
resetEmergency(chIndex);
|
||||||
});
|
});
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function emitForToggles(chIndex, toggles) {
|
function emitForToggles(chIndex, toggles) {
|
||||||
if (registerEmergency(chIndex, toggles)) return;
|
if (registerEmergency(chIndex, toggles)) return;
|
||||||
|
|
||||||
var type = "single";
|
var type = "single";
|
||||||
if (toggles >= 3) type = "triple";
|
if (toggles >= 3) type = "triple";
|
||||||
else if (toggles >= 2) type = "double";
|
else if (toggles >= 2) type = "double";
|
||||||
|
|
||||||
fireEvent(chIndex, type, toggles);
|
fireEvent(chIndex, type, toggles);
|
||||||
}
|
}
|
||||||
|
|
||||||
function onBurstEnd(chIndex) {
|
function onBurstEnd(chIndex) {
|
||||||
var s = chState[chIndex];
|
var s = chState[chIndex];
|
||||||
|
|
||||||
s.settling = false;
|
s.settling = false;
|
||||||
s.settleTimer = null;
|
s.settleTimer = null;
|
||||||
s.lastBurstEnd = Date.now();
|
s.lastBurstEnd = Date.now();
|
||||||
|
|
||||||
var changes = s.stateChanges;
|
var changes = s.stateChanges;
|
||||||
s.stateChanges = 0;
|
s.stateChanges = 0;
|
||||||
// v3.5: één btn_down = één fysieke toggle
|
|
||||||
|
// v3.5: one counted btn_down equals one physical toggle
|
||||||
var toggles = changes;
|
var toggles = changes;
|
||||||
if (toggles < 1) toggles = 1;
|
if (toggles < 1) toggles = 1;
|
||||||
|
|
||||||
print("[Burst] " + CONFIG.channels[chIndex].name +
|
print("[Burst] " + CONFIG.channels[chIndex].name +
|
||||||
": " + JSON.stringify(changes) + " counted events -> " +
|
": " + JSON.stringify(changes) + " counted events -> " +
|
||||||
JSON.stringify(toggles) + " toggles");
|
JSON.stringify(toggles) + " toggles");
|
||||||
|
|
||||||
if (s.waitingForLong) {
|
if (s.waitingForLong) {
|
||||||
var elapsed = Date.now() - s.singleTime;
|
var elapsed = Date.now() - s.singleTime;
|
||||||
|
|
||||||
s.waitingForLong = false;
|
s.waitingForLong = false;
|
||||||
|
|
||||||
if (s.longTimer !== null) {
|
if (s.longTimer !== null) {
|
||||||
Timer.clear(s.longTimer);
|
Timer.clear(s.longTimer);
|
||||||
s.longTimer = null;
|
s.longTimer = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (toggles === 1 &&
|
if (toggles === 1 &&
|
||||||
elapsed >= CONFIG.LONG_MIN_MS &&
|
elapsed >= CONFIG.LONG_MIN_MS &&
|
||||||
elapsed <= CONFIG.LONG_MAX_MS) {
|
elapsed <= CONFIG.LONG_MAX_MS) {
|
||||||
fireEvent(chIndex, "long", 1);
|
fireEvent(chIndex, "long", 1);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
fireEvent(chIndex, "single", 1);
|
fireEvent(chIndex, "single", 1);
|
||||||
|
|
||||||
if (toggles > 0) {
|
if (toggles > 0) {
|
||||||
emitForToggles(chIndex, toggles);
|
emitForToggles(chIndex, toggles);
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (toggles === 1 && CONFIG.LONG_ENABLED) {
|
if (toggles === 1 && CONFIG.LONG_ENABLED) {
|
||||||
s.waitingForLong = true;
|
s.waitingForLong = true;
|
||||||
s.singleTime = Date.now();
|
s.singleTime = Date.now();
|
||||||
|
|
||||||
print("[Long] Waiting for return toggle " + CONFIG.channels[chIndex].name);
|
print("[Long] Waiting for return toggle " + CONFIG.channels[chIndex].name);
|
||||||
|
|
||||||
s.longTimer = Timer.set(CONFIG.LONG_MAX_MS, false, function() {
|
s.longTimer = Timer.set(CONFIG.LONG_MAX_MS, false, function() {
|
||||||
s.waitingForLong = false;
|
s.waitingForLong = false;
|
||||||
s.longTimer = null;
|
s.longTimer = null;
|
||||||
|
|
||||||
print("[Long] Timeout -> single");
|
print("[Long] Timeout -> single");
|
||||||
emitForToggles(chIndex, 1);
|
emitForToggles(chIndex, 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
emitForToggles(chIndex, toggles);
|
emitForToggles(chIndex, toggles);
|
||||||
}
|
}
|
||||||
|
|
||||||
Shelly.addEventHandler(function(event) {
|
Shelly.addEventHandler(function(event) {
|
||||||
if (event.component === undefined) return;
|
if (event.component === undefined) return;
|
||||||
if (event.info === undefined) return;
|
if (event.info === undefined) return;
|
||||||
|
|
||||||
var chIndex = -1;
|
var chIndex = -1;
|
||||||
|
|
||||||
for (var i = 0; i < CONFIG.channels.length; i++) {
|
for (var i = 0; i < CONFIG.channels.length; i++) {
|
||||||
if (event.component === ("input:" + CONFIG.channels[i].inputId)) {
|
if (event.component === ("input:" + CONFIG.channels[i].inputId)) {
|
||||||
chIndex = i;
|
chIndex = i;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (chIndex === -1) return;
|
if (chIndex === -1) return;
|
||||||
|
|
||||||
var signal = null;
|
var signal = null;
|
||||||
|
|
||||||
if (event.info.state !== undefined) {
|
if (event.info.state !== undefined) {
|
||||||
signal = event.info.state;
|
signal = event.info.state;
|
||||||
} else if (event.info.event !== undefined) {
|
} else if (event.info.event !== undefined) {
|
||||||
|
|
@ -194,36 +235,42 @@ Shelly.addEventHandler(function(event) {
|
||||||
} else {
|
} else {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var now = Date.now();
|
var now = Date.now();
|
||||||
var s = chState[chIndex];
|
var s = chState[chIndex];
|
||||||
var other = chState[(chIndex === 0) ? 1 : 0];
|
var other = chState[(chIndex === 0) ? 1 : 0];
|
||||||
// Crosstalk-beveiliging: blokkeer het eerste event van dit kanaal als het
|
|
||||||
// andere kanaal net actief was. Voorkomt vals triggeren bij Gira Systeem 55
|
|
||||||
// dubbele schakelaars.
|
|
||||||
if (s.stateChanges === 0 && !s.settling) {
|
if (s.stateChanges === 0 && !s.settling) {
|
||||||
if (other.settling) return;
|
if (other.settling) return;
|
||||||
if (now - other.lastBurstEnd < CONFIG.CROSSTALK_MS) return;
|
if (now - other.lastBurstEnd < CONFIG.CROSSTALK_MS) return;
|
||||||
}
|
}
|
||||||
// Tel alleen het ingestelde signaal. Standaard: btn_down.
|
|
||||||
|
// Count only selected signal. Default: btn_down.
|
||||||
if (signal === CONFIG.COUNT_SIGNAL || event.info.state !== undefined) {
|
if (signal === CONFIG.COUNT_SIGNAL || event.info.state !== undefined) {
|
||||||
s.stateChanges += 1;
|
s.stateChanges += 1;
|
||||||
s.settling = true;
|
s.settling = true;
|
||||||
|
|
||||||
print("[State] " + CONFIG.channels[chIndex].name +
|
print("[State] " + CONFIG.channels[chIndex].name +
|
||||||
" = " + JSON.stringify(signal) +
|
" = " + JSON.stringify(signal) +
|
||||||
" (#" + JSON.stringify(s.stateChanges) + ")");
|
" (#" + JSON.stringify(s.stateChanges) + ")");
|
||||||
|
|
||||||
if (s.settleTimer !== null) {
|
if (s.settleTimer !== null) {
|
||||||
Timer.clear(s.settleTimer);
|
Timer.clear(s.settleTimer);
|
||||||
}
|
}
|
||||||
|
|
||||||
s.settleTimer = Timer.set(CONFIG.SETTLE_MS, false, function() {
|
s.settleTimer = Timer.set(CONFIG.SETTLE_MS, false, function() {
|
||||||
onBurstEnd(chIndex);
|
onBurstEnd(chIndex);
|
||||||
});
|
});
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (signal === "btn_up") {
|
if (signal === "btn_up") {
|
||||||
print("[Ignored] " + CONFIG.channels[chIndex].name +
|
print("[Ignored] " + CONFIG.channels[chIndex].name +
|
||||||
" = " + JSON.stringify(signal));
|
" = " + JSON.stringify(signal));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (signal === "single_push" ||
|
if (signal === "single_push" ||
|
||||||
signal === "double_push" ||
|
signal === "double_push" ||
|
||||||
signal === "triple_push" ||
|
signal === "triple_push" ||
|
||||||
|
|
@ -233,8 +280,7 @@ Shelly.addEventHandler(function(event) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
print("=== Shelly 2PM Detached v3.5 ===");
|
print("=== Shelly Detached v3.5 ===");
|
||||||
print("Geschikt voor: Gen4 2PM, Gen3 2PM");
|
|
||||||
print("Device: " + deviceName);
|
print("Device: " + deviceName);
|
||||||
print("Timing: settle=" + JSON.stringify(CONFIG.SETTLE_MS) +
|
print("Timing: settle=" + JSON.stringify(CONFIG.SETTLE_MS) +
|
||||||
"ms crosstalk=" + JSON.stringify(CONFIG.CROSSTALK_MS) + "ms");
|
"ms crosstalk=" + JSON.stringify(CONFIG.CROSSTALK_MS) + "ms");
|
||||||
|
|
@ -242,10 +288,12 @@ print("Counting only: " + CONFIG.COUNT_SIGNAL);
|
||||||
print("Long: " + (CONFIG.LONG_ENABLED ? "enabled" : "disabled"));
|
print("Long: " + (CONFIG.LONG_ENABLED ? "enabled" : "disabled"));
|
||||||
print("Emergency: " + CONFIG.EMERGENCY_COUNT + " toggles / " +
|
print("Emergency: " + CONFIG.EMERGENCY_COUNT + " toggles / " +
|
||||||
CONFIG.EMERGENCY_WINDOW_MS + "ms");
|
CONFIG.EMERGENCY_WINDOW_MS + "ms");
|
||||||
|
|
||||||
for (var pi = 0; pi < CONFIG.channels.length; pi++) {
|
for (var pi = 0; pi < CONFIG.channels.length; pi++) {
|
||||||
var ch = CONFIG.channels[pi];
|
var ch = CONFIG.channels[pi];
|
||||||
print(" " + ch.name +
|
print(" " + ch.name +
|
||||||
" input:" + ch.inputId +
|
" input:" + ch.inputId +
|
||||||
" -> output:" + ch.outputId);
|
" -> output:" + ch.outputId);
|
||||||
}
|
}
|
||||||
|
|
||||||
print("[Script] Active");
|
print("[Script] Active");
|
||||||
|
|
@ -1,238 +0,0 @@
|
||||||
// =============================================================================
|
|
||||||
// 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");
|
|
||||||
|
|
@ -1,181 +0,0 @@
|
||||||
// =============================================================================
|
|
||||||
// Shelly 1PM Detached Script v3.7
|
|
||||||
// Native click events + custom emergency
|
|
||||||
// =============================================================================
|
|
||||||
|
|
||||||
var CONFIG = {
|
|
||||||
channels: [
|
|
||||||
{ inputId: 0, outputId: 0 }
|
|
||||||
],
|
|
||||||
|
|
||||||
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({
|
|
||||||
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
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
|
||||||
var s = chState[chIndex];
|
|
||||||
|
|
||||||
if (s.emergencyLocked) return;
|
|
||||||
|
|
||||||
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) + " clicks");
|
|
||||||
resetEmergency(chIndex);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
s.emergencyCount += 1;
|
|
||||||
|
|
||||||
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);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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.event !== undefined) {
|
|
||||||
signal = event.info.event;
|
|
||||||
} else if (event.info.state !== undefined) {
|
|
||||||
signal = event.info.state;
|
|
||||||
} else {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (signal === "btn_down") {
|
|
||||||
print("[Count] " + CONFIG.channels[chIndex].name + " = btn_down");
|
|
||||||
registerEmergency(chIndex);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (signal === "btn_up") {
|
|
||||||
print("[Ignored] " + CONFIG.channels[chIndex].name + " = btn_up");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (signal === "single_push") {
|
|
||||||
fireEvent(chIndex, "single", 1);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (signal === "double_push") {
|
|
||||||
fireEvent(chIndex, "double", 2);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (signal === "triple_push") {
|
|
||||||
fireEvent(chIndex, "triple", 3);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (signal === "long_push") {
|
|
||||||
fireEvent(chIndex, "long", 1);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
print("[Ignored] " + CONFIG.channels[chIndex].name +
|
|
||||||
" = " + JSON.stringify(signal));
|
|
||||||
});
|
|
||||||
|
|
||||||
print("=== Shelly 1PM Detached v3.7 ===");
|
|
||||||
print("Device: " + deviceName);
|
|
||||||
print("Mode: native click events + custom emergency");
|
|
||||||
print("Emergency: " + CONFIG.EMERGENCY_COUNT + " clicks / " +
|
|
||||||
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");
|
|
||||||
Loading…
Reference in a new issue