Скрипт отработки разных типов нажатий

Добрый день!

Помогите, пожалуйста, правильно написать скрипт для отработки разных типов нажатий кнопки.

Дано: WB 6.6.0, свет подключен к WB-MR-6C - выход K5, к входу Input 5 подключена кнопка без фиксации, двигатель штор подключен к WBIO-DO-R10R-4 (контролы - wb-gpio/EXT1_DIR3, wb-gpio/EXT1_ON3).
Требуется: при одинарном нажатии включать выход К5, при двойном нажатии открывать шторы, если закрыты и закрывать, если открыты).

Что делал:

Остановил MQTT:

service wb-mqtt-serial stop

Отключил взаимодействие входа Input 5 с выходом K5:

modbus_client --debug -mrtu -pnone -s2 /dev/ttyRS485-1 -a48 -t0x03 -r13 3

Нашел скрипт от @almo

'use strict';

var ActionButtons = {};


/**
 * Version: 0.3
 * 
 * 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 "triplePress" or "longPress" or "longRelease".
 *                                  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"]},
 *                                      triplePress: {func: myFunc3, prop: []},
 *                                      longPress: {func: myFunc4, prop: []},
 *                                      longRelease: {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).
 * @param  {number} intervalOfRepeat - Time (ms) before repeating action specified in LongPress action. Default is 50 ms.
 * 
 * Note: In case longRelease function defined, longPress function will repeate till button is released.
 *       In case longRelease function not defined, only one action will be executed for longPress.
 */
ActionButtons.onButtonPress = function (trigger, action, timeToNextPress, timeOfLongPress, intervalOfRepeat) {
    
    // Set default values if not passed into function
    timeToNextPress = timeToNextPress || 300;
    timeOfLongPress = timeOfLongPress || 1000;
    intervalOfRepeat = intervalOfRepeat || 100;
    
    var buttonPressedCounter = 0;
    var actionRepeatCounter = 0;
    var timerWaitNextShortPress = null;
    var timerLongPress = null;
    var timerWaitLongRelease = null;
    var isLongPressed = false;
    var isLongReleased = false;

    var ruleName = "on_button_press_" + trigger.replace("/", "_");
    log("LOG::Define WB Rule: ", ruleName);

    defineRule(ruleName, {
        whenChanged: trigger,
        then: function (newValue, devName, cellName) {

            // If button is pressed, wait for a long press
            if (newValue) {

                if (timerWaitNextShortPress) {
                    clearTimeout(timerWaitNextShortPress);
                }
                timerLongPress = setTimeout(function () {
                    isLongPressed = true;  // Long press identified, we will skip short press
                    isLongReleased = false;
                    buttonPressedCounter = 0;
                    actionRepeatCounter = 1;
                    if (typeof action.longPress === "object") {
                        if (typeof action.longPress.func === "function") {
                            action.longPress.func.apply(this, action.longPress.prop);
                            // If Long Release action define, we will repeat Long Press action till not released. Otherwise only 1 Long Press action is executed
                            if (typeof action.longRelease === "object") {
                                if (typeof action.longRelease.func === "function") {
                                    timerWaitLongRelease = setInterval(function () {
                                        if(!isLongReleased) {
                                            if (typeof action.longPress === "object") {
                                                if (typeof action.longPress.func === "function") {
                                                    action.longPress.func.apply(this, action.longPress.prop);
                                                }
                                            }
                                            // log(">>>>>> long press - press (" + actionRepeatCounter++ + ") <<<<<<");    
                                        }
                                        if(isLongReleased) {
                                            clearInterval(timerWaitLongRelease);
                                        }
                                    }, intervalOfRepeat);        
                                }                                        
                            }
    
                        }
                    }
                    // log(">>>>>> long press - press (" + actionRepeatCounter++ + ") <<<<<<");
                }, timeOfLongPress);

            }

            // If button is released, then it is not a "long press", start to count clicks
            else {
                if (!isLongPressed) {
                    if (timerLongPress) {
                        clearTimeout(timerLongPress);
                    }
                    buttonPressedCounter += 1;
                    timerWaitNextShortPress = setTimeout(function () {
                        switch (buttonPressedCounter) {
                        // Counter equals 1 - it's a single short press
                        case 1:
                            if (typeof action.singlePress === "object") {
                                if (typeof action.singlePress.func === "function") {
                                    action.singlePress.func.apply(this, action.singlePress.prop);
                                }
                            }
                            // log(">>>>>> short press - single <<<<<<");
                            break;
                        // Counter equals 2 - it's a double short press
                        case 2:
                            if (typeof action.doublePress === "object") {
                                if (typeof action.doublePress.func === "function") {
                                    action.doublePress.func.apply(this, action.doublePress.prop);
                                }
                            }
                            // log(">>>>>> short press - double <<<<<<");
                            break;
                        // Counter equals 3 - it's a triple short press
                        case 3:
                            if (typeof action.triplePress === "object") {
                                if (typeof action.triplePress.func === "function") {
                                    action.triplePress.func.apply(this, action.triplePress.prop);
                                }
                            }
                            // log(">>>>>> short press - triple <<<<<<");
                            break;
                        }
                        // Reset the counter
                        buttonPressedCounter = 0;
                    }, timeToNextPress);
                }

                // Catch button released after long press
                else {
                    if (typeof action.longRelease === "object") {
                        if (typeof action.longRelease.func === "function") {
                            // if (typeof action.longRelease.prop === "array") {
                                action.longRelease.func.apply(this, action.longRelease.prop);
                            // } else {
                            //     action.longRelease.func.apply(this, []);
                            // }
                        }
                    }
                    // log(">>>>>> long press - release <<<<<<");
                    isLongPressed = false;
                    isLongReleased = 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-mr6c_48/Input 5",
{
    singlePress: {
        func: switchRelay,
        prop: ["wb-mr6c_48", "K5"]
    },
    doublePress: {
        func: switchCurtain,
        prop: ["wb-gpio", "EXT1_DIR3"]
    },
 
},
300, 1000
);

Скрипт не работает, предполагал, что из-за скорости, но после изменения на 115200 тоже не заработало.

Помогите, пожалуйста, корректно описать управление шторами.

Добрый день… Считаю что сейчас программная обработка типов нажатий - неактуальна, предлагаю просто поставить прошивку реле отсюда:

и использовать аппаратную обработку.

1 Like

Я не совсем понял, как не используя скрипт, управлять контролами на соседнем устройстве. После перепрошивки модуля мне станет доступна расширенная маппинг матрица, а как дальше без скрипта этим управлять?

Здравствуйте!
Без правил (скрипта) можно управлять только каналами реле самого модуля. Для управления каналами другого модуля можно из правил опрашивать счетчик требуемого типа нажатия модуля реле. По его изменению уже выполнять требуемое действия. Уже не потребуется писать такое сложное правило, как в примере выше.

Идею понял, но, боюсь, мне не хватает знаний в написании скриптов. Подскажите, может быть есть специалисты, которые могут писать скрипты на коммерческой основе?

Болше 3/4 скрипта выполняет именно программное распознавание длительности нажатия. Я предлагаю от этого отказаться, скрипт примет вид не сложнее чем
https://wirenboard.com/wiki/Rule_Examples#Слежение_за_контролом
Попробуйте, выложите свой вариант - помогу.

Написал такой скрипт, но он не работает :frowning:

defineRule(“curtain_livingroom”, {
whenChanged: “wb-mr6c_48/input_5 Long Press Counter”,
then: function (newValue, devName, cellName) {
dev[“wb-gpio”][“EXT1_ON3”] = newValue;
dev[“wb-gpio”][“EXT1_DIR3”] = newValue;

}
});

Как следует из описания - в newValue передается новое значение топика, на изменение которого настроено срабатывание правила.
Добавьте строку

log/info("Правило curtain_livingroom; newValue:", newValue);

в функцию и увидите что пытаетесь установить реле в значение счетчика.
надо

dev[“wb-gpio”][“EXT1_ON3”] = true;

например. Ну и предусмотреть таймер, по которому отключится двигатель если не будет других команд. Или таймер в другом скрипте и срабатывает на включение двигателя?

Допилил скрипт:

var timer_id = null;
var timeout_s = 10*1000;

defineRule("curtain_livingroom", { 
      whenChanged: "wb-mr6c_48/input_5 Long Press Counter",
      then: function(newValue, devName, cellName) {
          
               dev["wb-gpio/EXT1_ON3"] = true;
               dev["wb-gpio/EXT1_DIR3"] = !dev["wb-gpio/EXT1_DIR3"];
               if (timer_id) {
                   clearTimeout(timer_id);
                }
               timer_id =  setTimeout(function () {
                    dev["wb-gpio/EXT1_ON3"] = false;
                    timer_id = null;
               }, timeout_s);
         
      }
});

Возможно я неправильно понимаю логику работы управления через сухой контакт, поймал баг, когда при управлении шторами через скрипт перестает работать управление через spruthub, т.е. как-будто положение штор не изменилось в sh, а по факту шторы закрываются и открываются.

Мне бы помогло описание работы контролов, например:

Для закрытия штор:

EXT1_DIR3 - false
EXT1_ON3 - true

После закрытия контролы должны вернуться в:

EXT1_DIR3 - false
EXT1_ON3 - false

Для открытия штор:

EXT1_DIR3 - true
EXT1_ON3 - true

После открытия контролы должны вернуться в:

EXT1_DIR3 - false
EXT1_ON3 - true

Судя по скрипту логика простая: какждый раз когда фиксируется ажатие реле направления переключается и основное реле включается на время, ограниченное таймером.
А что не так работает?

Как это выглядит? Немного не пойму о чем речь. Не вижу, где хранится именно состояние штор.

Так… Объясните другими словами, что имеете в виду?
Возможно, обернуть шторы ы виртуальное устройство?

А такое - недопустимо, EXT1_ON3 должно быть “true” только на вермя движения.

Со скриптом разобрался, работает, но обнаружил один недостаток использования функции “whenChanged” - при сохранении правила или при обновлении прошивки WB скрипт срабатывает (соответственно закрываются все шторы). Есть ли какой-то способ избежать этого неудобства?

Думаю, только отсрочкой создания правил при запуска. Например создавать именно правила после отрабатывания таймера.

Здравствуйте!

Мне кажется это довольно странным. Вы уверены, что при сохранении правила оно выполняется?

Вот тестирую пример:

defineRule("curtain_livingroom", { 
      whenChanged: "wb-mr6c_72/input_2 Counter",
      then: function(newValue, devName, cellName) {
          
               dev["wb-mr6c_72/K2"] = !dev["wb-mr6c_72/K2"];
               log("whenСhanged executed");
         
      }
});

При сохранении правила оно не выполняется. Выполняется только при изменении счетчика срабатываний входа модуля реле.