Обновил скрипт, найденный здесь, на просторах форума.
Скрипт позволяет обрабатывать нажатия как “сухих контактов” (A1-4, WBIO-DI-WD-14, MCM8 …), так и переназначать нажатия с одних устройств на другие (например “wb-led_XXX/Input 1”)
В исходный модуль ActionButtons добавлены новые функции, расширена логика обработки нажатий и устранена потенциальная ошибка работы с таймерами.
- Добавлена функция
switchOffAll
- Принимает произвольное количество аргументов в формате
"устройство/контрол", перечисленных в формате wirenboard через запятую. Например, чтобы выключить все устройства в комнате. - Автоматически определяет тип текущего значения и выключает устройство:
boolean→false(реле/переключатель)- строка
"R;G;B"→"0;0;0"(RGB-лента) number→0(диммер/уровень)
- Включает валидацию формата и проверку существования устройства.
- Реализован обработчик
shortLongPress
- Распознаёт последовательность: короткое нажатие → быстрое второе нажатие → удержание .
- Работает параллельно с
singlePress,doublePress,longPressиlongRelease. - Настройки чувствительности задаются через
timeToNextPress(окно между нажатиями) иtimeOfLongPress(порог удержания).
- Исправлена ошибка
ERROR: trying to stop unknown timer
- Причина: В
wb-rulesвызовclearTimeout()на уже отработавшем таймере генерирует ошибку в системном логе. Номер таймера рос с каждой обработкой, так как ссылка на него не сбрасывалась.- Добавлена функция
safeClear(), которая проверяет ссылку наnullи оборачиваетclearTimeoutвtry...catch. - Все переменные таймеров принудительно обнуляются (
= null) сразу после выполнения колбэкаsetTimeout. - Состояние кнопки инкапсулировано в объект
state, что исключает конфликты замыканий. - Добавлены проверки типов и безопасные значения по умолчанию для параметров таймаутов.
- Добавлена функция
(function () {
'use strict';
var ActionButtons = {};
/**
* Function that identifies what kind of press was performed: single, double or long press;
* and assigns an action for each type of press.
*
* @param {string} trigger - Name of device and control in the following format: "<device>/<control>".
* @param {object} action - Defines actions to be taken for each type of button press.
* Key: "singlePress" or "doublePress" or "longPress" or "longRelease" or "shortLongPress".
* Value: Object of the following structure {func: <function name>, prop: <array of parameters to be passed>}
* Example:
* {
* singlePress: {func: myFunc1, prop: ["wb-mr6c_1", "K1"]},
* doublePress: {func: myFunc2, prop: ["wb-mrgbw-d_2", "RGB", "255;177;85"]},
* longPress: {func: myFunc3, prop: []},
* longRelease: {func: myFunc4, prop: []}
* shortLongPress: {func: myFunc5, prop: []}
* }
* @param {number} timeToNextPress - Time (ms) after button up to wait for the next press before reseting the counter. Default is 300 ms.
* @param {number} timeOfLongPress - Time (ms) after button down to be considered as as a long press. Default is 1000 ms (1 sec).
*/
ActionButtons.onButtonPress = function (trigger, action, timeToNextPress, timeOfLongPress) {
var state = {
counter: 0,
timerShort: null,
timerLong: null,
isLong: false,
waitingShortLong: false
};
timeToNextPress = (typeof timeToNextPress === 'number') ? timeToNextPress : 300;
timeOfLongPress = (typeof timeOfLongPress === 'number') ? timeOfLongPress : 1000;
// Безопасная очистка таймера
function safeClear(timerRef) {
if (timerRef !== null) {
try { clearTimeout(timerRef); } catch(e) {}
}
return null; // всегда возвращаем null
}
defineRule("btn_" + trigger.replace(/[\/]/g, "_"), {
whenChanged: trigger,
then: function (newValue) {
// ================= КНОПКА НАЖАТА =================
if (newValue) {
state.timerShort = safeClear(state.timerShort);
// Если ждём продолжения "короткое + длинное"
if (state.waitingShortLong) {
state.timerLong = setTimeout(function () {
if (action.shortLongPress && typeof action.shortLongPress.func === "function") {
action.shortLongPress.func.apply(this, action.shortLongPress.prop || []);
// log.info("shortLongPress triggered");
}
state.timerLong = null;
state.waitingShortLong = false;
state.counter = 0;
state.isLong = true;
}, timeOfLongPress);
return;
}
// Обычное первое нажатие (длинное)
state.timerLong = setTimeout(function () {
if (action.longPress && typeof action.longPress.func === "function") {
action.longPress.func.apply(this, action.longPress.prop || []);
// log.info("longPress triggered");
}
state.timerLong = null;
state.isLong = true;
state.counter = 0;
}, timeOfLongPress);
}
// ================= КНОПКА ОТПУЩЕНА =================
else {
// Отпускание после длинного нажатия
if (state.isLong) {
state.timerLong = safeClear(state.timerLong);
if (action.longRelease && typeof action.longRelease.func === "function") {
action.longRelease.func.apply(this, action.longRelease.prop || []);
// log.info("longRelease triggered");
}
state.isLong = false;
state.waitingShortLong = false;
state.counter = 0;
return;
}
// Отмена таймера длинного нажатия (было короткое)
state.timerLong = safeClear(state.timerLong);
state.counter += 1;
// Таймер ожидания следующего клика
state.timerShort = safeClear(state.timerShort);
state.timerShort = setTimeout(function () {
if (state.counter === 1) {
if (action.singlePress && typeof action.singlePress.func === "function") {
action.singlePress.func.apply(this, action.singlePress.prop || []);
// log.info("singlePress triggered");
}
} else if (state.counter === 2) {
if (action.doublePress && typeof action.doublePress.func === "function") {
action.doublePress.func.apply(this, action.doublePress.prop || []);
// log.info("doublePress triggered");
}
}
state.timerShort = null;
state.waitingShortLong = false;
state.counter = 0;
}, timeToNextPress);
// Если после 1-го клика есть обработчик shortLongPress, помечаем ожидание
if (state.counter === 1 && action.shortLongPress) {
state.waitingShortLong = true;
}
}
}
});
};
// export as Node module / AMD module / browser variable
if (typeof exports === 'object' && typeof module !== 'undefined') {
module.exports = ActionButtons;
} else if (typeof define === 'function' && define.amd) {
define(ActionButtons);
} else {
global.ActionButtons = ActionButtons;
}
}());
ActionButtons.onButtonPress(
"wb-gpio/EXT2_IN4", //Вход, за которым следим.
{
singlePress: {
//func: switchRelay, prop: ["wb-led_227", "Channel 3"]
},
doublePress: {
//func: switchRelay, prop: ["wb-mio-gpio_157:2", "K2"]
//func: switchDimmerRGB, prop: ["wb-mr6c_10", "K2", "wb-mrgbw-d_24"]
},
longPress: {
//func: switchRelay, prop: ["wb-mio-gpio_157:2", "K3"]
//func: setRandomRGB, prop: ["wb-mr6c_10", "K2", "wb-mrgbw-d_24"]
//func: switchOffAll, prop: ["wb-led_224/Channel 3", "wb-led_206/Channel 4"]
},
shortLongPress: {
//func: switchRelay, prop: ["wb-mio-gpio_157:2", "K3"]
//func: setRandomRGB, prop: ["wb-mr6c_10", "K2", "wb-mrgbw-d_24"]
//func: switchOffAll, prop: ["wb-led_224/Channel 3", "wb-led_206/Channel 4"]
}
},
300, 1000
);
/**
* Helper Functions
*/
function switchRelay(device, control) { //Принимает в параметрах устройство и выход. Переключает состояние выхода на противоположное.
log.info("LongPress switchRelay" ,device, control) //Это лог. Он попадает в /var/log/messages
dev[device][control] = !dev[device + "/" + control];
}
function switchDimmerRGB(relayDevice, relayControl, dimmerDevice) {
dev[relayDevice][relayControl] = true;
if (dev[dimmerDevice + "/RGB"] !== "0;0;0") {
dev[dimmerDevice]["RGB"] = "0;0;0";
}
else {
dev[dimmerDevice]["RGB"] = dev[relayDevice + "/RGB"];
}
}
function setRandomRGB(relayDevice, relayControl, dimmerDevice) {
dev[relayDevice][relayControl] = true;
dev[relayDevice + "/RGB"] = "" + Math.floor(Math.random() * 255) + ";" + Math.floor(Math.random() * 255) + ";" + Math.floor(Math.random() * 255);
dev[dimmerDevice]["RGB"] = dev[relayDevice + "/RGB"];
}
function switchOffAll() {
for (var i = 0; i < arguments.length; i++) {
var arg = arguments[i];
// Пропускаем нестроковые аргументы
if (typeof arg !== "string") {
log.warn("LongPress switchOffAll: argument", i, "is not a string, skipping");
continue;
}
// Парсим формат "device/control"
var separatorIndex = arg.indexOf("/");
if (separatorIndex === -1) {
log.warn("LongPress switchOffAll: invalid format for argument", i,
"- expected 'device/control', got:", arg);
continue;
}
var device = arg.substring(0, separatorIndex);
var control = arg.substring(separatorIndex + 1);
// Проверяем существование устройства
if (!dev[device]) {
log.warn("LongPress switchOffAll: device not found", device);
continue;
}
var currentValue = dev[device + "/" + control];
// Выключаем в зависимости от типа значения
if (typeof currentValue === "boolean") {
// Реле/переключатель → false
dev[device][control] = false;
}
else if (typeof currentValue === "string" && currentValue.match(/^\d+;\d+;\d+$/)) {
// RGB-значение → "0;0;0" (чёрный)
dev[device][control] = "0;0;0";
}
else if (typeof currentValue === "number") {
// Диммер/яркость → 0
dev[device][control] = 0;
}
else {
// Fallback: пробуем выключить
dev[device][control] = false;
}
}
}
Проверено на Wiren Board 8.4.3 release wb-2606 (as testing)
Буду рад замечаним, дополнениям, исправлениям