Перейти к содержанию

Навигация

Rule Examples: различия между версиями

(не показано 13 промежуточных версий 5 участников)
Строка 1: Строка 1:
<languages/>
<languages/>
<translate>
<translate>
<!--T:250-->
<!--T:255-->
{{DISPLAYTITLE: Примеры правил}}
{{DISPLAYTITLE: Примеры правил}}


== Общая информация ==
== Общая информация ==
Здесь вы найдёте учебные примеры скриптов, написанных для движка правил [[wb-rules| wb-rules]].
Здесь вы найдёте учебные примеры скриптов, написанных для движка правил '''[[wb-rules| wb-rules]]'''.


Алгоритмы в примерах предельно просты и не учитывают многих факторов которые могут возникнуть в реальности. Поэтому используйте эту библиотеку только как учебный материал, а не источник готовых скриптов для реальных проектов.
Алгоритмы в примерах предельно просты и не учитывают многих факторов которые могут возникнуть в реальности. Поэтому используйте эту библиотеку только как учебный материал, а не источник готовых скриптов для реальных проектов.
== Виртуальное устройство ==
Виртуальное устройство можно использовать для объединения каналов, задания особой логики для устройства или просто так для красоты.
Пример ниже создаст виртуальное устройство с именем '''deviceName''' и двумя контролами '''value''' и '''state'''. А благодаря правилу с '''whenChanged''', значение контрола '''state''' будет менять в зависимости от значение контрола '''value'''.
<syntaxhighlight lang="ecmascript">
deviceName = 'my-virtual-device';
defineVirtualDevice(deviceName, {
    title: {'en': 'My Virtual Device', 'ru': 'Мое виртуальное устройство'} ,
    cells: {
      value: {
        title: {'en': 'Value', 'ru': 'Значение'},
        type: "range",
        value: 1,
        max: 3,
        min: 1
      },
      state: {
        title: {'en': 'State', 'ru': 'Состояние'},
        type: "value",
        value: 1,
        enum:{
          1: {'en': 'Normal', 'ru': 'В норме'},
          2: {'en': 'Warning', 'ru': 'Внимание'},
          3: {'en': 'Crash', 'ru': 'Авария'}} 
      },
    }
});
defineRule({
  whenChanged: deviceName+"/value",
  then: function (newValue, devName, cellName) {
dev[deviceName+"/state"] = newValue;
  }
});
</syntaxhighlight>


== Слежение за контролом == <!--T:20-->
== Слежение за контролом == <!--T:20-->
Строка 67: Строка 107:
</syntaxhighlight>
</syntaxhighlight>


== Детектор движения c таймаутом == <!--T:34-->
== Мастер-выключатель с восстановлением последнего состояния == <!--T:250-->
 
На вход контроллера подключен мастер-выключатель, который, при переключении, отключает все устройства, указанные в соответствующем правиле. При повторном нажатии на выключатель, устройствам возвращается первоначальное состояние.


<!--T:35-->
Подключение осуществляется к контакту A1 и 5V на контроллере. При замыкании на соответствующем канале <code>wb-gpio/A1_IN</code>, состояние меняется, и срабатывает правило.
На вход D2 подключен детектор движения с выходом «сухой контакт». При обнаружении движения он замыкает D2 и GND, и на соответствующем канале <code>wb-gpio/D2_IN</code> появляется статус «1».


<!--T:37-->
Для управления через веб-интерфейс создано виртуальное устройство, отображаемое на вкладке '''Устройства'''.
Освещение подключено через встроенное реле, соответствующий канал <code>wb-gpio/Relay_1</code>.


<!--T:36-->
Первоначальные состояния устройств сохраняются в [https://github.com/wirenboard/wb-rules#%D0%BF%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%BD%D0%BE%D0%B5-%D1%85%D1%80%D0%B0%D0%BD%D0%B8%D0%BB%D0%B8%D1%89%D0%B5 постоянном хранилище]. Переменные в постоянном хранилище записываются на флеш-память, что обеспечивает доступ к ним после перезагрузки контроллера.
Правило работает так:
* когда движение появляется, свет включается. Если ранее был запущен тридцатисекундный таймер «на выключение», этот таймер отключается;
* когда движение пропадает, запускается тридцатисекундный таймер «на выключение». Если ему удаётся дойти до конца, свет выключается.


<!--T:38-->
<syntaxhighlight lang="ecmascript">
<syntaxhighlight lang="ecmascript">
var motion_timer_1_timeout_ms = 30 * 1000;
defineVirtualDevice("power_off", {
var motion_timer_1_id = null;
    title: "Мастер-выключатель",
    cells: {
        power_off: {
            type: "pushbutton"
        },
    }
});
 
var ps = new PersistentStorage("power-storage", { global: true });
var lights = ["wb-mdm3_50/K1", "wb-mdm3_50/K2", "wb-mdm3_50/K3"];
 
var isPowerOff = true;


<!--T:191-->
defineRule({
defineRule("motion_detector_1", {
     whenChanged: ["wb-gpio/A1_IN", "power_off/power_off"],
     whenChanged: "wb-gpio/D2_IN",
     then: function (newValue, devName, cellName) {
     then: function (newValue, devName, cellName) {
         if (newValue) {
         if (isPowerOff) {
             dev["wb-gpio/Relay_1"] = true;
             lights.forEach(function (light) {
             if (motion_timer_1_id) {
                ps[light] = dev[light];
                clearTimeout(motion_timer_1_id);
                dev[light] = false;
            }
             });
             motion_timer_1_id = setTimeout(function () {
        } else {
                 dev["wb-gpio/Relay_1"] = false;
             lights.forEach(function (light) {
                motion_timer_1_id = null;
                 dev[light] = ps[light];
             }, motion_timer_1_timeout_ms);
             });
         }
         }
     },
        isPowerOff = !isPowerOff;
     }
});
});
</syntaxhighlight>
</syntaxhighlight>


== Создание однотипных правил == <!--T:43-->
== Детектор движения c таймаутом == <!--T:34-->


<!--T:44-->
<!--T:35-->
Если таких детекторов движения нужно несколько, то, чтобы не копировать код, можно обернуть создание правила и переменных в функцию:
На вход D2 подключен детектор движения с выходом «сухой контакт». При обнаружении движения он замыкает D2 и GND, и на соответствующем канале <code>wb-gpio/D2_IN</code> появляется статус «1».
 
<!--T:37-->
Освещение подключено через встроенное реле, соответствующий канал <code>wb-gpio/Relay_1</code>.
 
<!--T:36-->
Правило работает так:
* когда движение появляется, свет включается. Если ранее был запущен тридцатисекундный таймер «на выключение», этот таймер отключается;
* когда движение пропадает, запускается тридцатисекундный таймер «на выключение». Если ему удаётся дойти до конца, свет выключается.


<!--T:45-->
<!--T:38-->
<syntaxhighlight lang="ecmascript">
<syntaxhighlight lang="ecmascript">
function makeMotionDetector(name, timeout_ms, detector_control, relay_control) {
var motion_timer_1_timeout_ms = 30 * 1000;
  var motion_timer_id = null;
var motion_timer_1_id = null;
  defineRule(name, {
      whenChanged: "wb-gpio/" + detector_control,
      then: function(newValue, devName, cellName) {
          if (!newValue) {
              dev["wb-gpio/relay_control"] = true;
              if (motion_timer_id) {
                  clearTimeout(motion_timer_id);
              }


              <!--T:46-->
<!--T:191-->
motion_timer_id = setTimeout(function() {
defineRule("motion_detector_1", {
                  dev["wb-gpio/relay_control"] = false;
    whenChanged: "wb-gpio/D2_IN",
                  motion_timer_id = null;
    then: function (newValue, devName, cellName) {
              }, timeout_ms);
        if (newValue) {
          }
            dev["wb-gpio/Relay_1"] = true;
      }
            if (motion_timer_1_id) {
  });
                clearTimeout(motion_timer_1_id);
}
            }
 
            motion_timer_1_id = setTimeout(function () {
<!--T:47-->
                dev["wb-gpio/Relay_1"] = false;
makeMotionDetector("motion_detector_1", 20000, "EXT1_DR1", "EXT2_R3A1");
                motion_timer_1_id = null;
makeMotionDetector("motion_detector_2", 10000, "EXT1_DR2", "EXT2_R3A2");
            }, motion_timer_1_timeout_ms);
makeMotionDetector("motion_detector_3", 10000, "EXT1_DR3", "EXT2_R3A3");
        }
    },
});
</syntaxhighlight>
</syntaxhighlight>


== Активация правила только в определённое время == <!--T:48-->
== Создание однотипных правил == <!--T:43-->


<!--T:49-->
<!--T:44-->
Правило как в предыдущем разделе, но выполняется только с 9:30 до 17:10 по UTC.
Если таких детекторов движения нужно несколько, то, чтобы не копировать код, можно обернуть создание правила и переменных в функцию:


<!--T:50-->
<!--T:45-->
<syntaxhighlight lang="ecmascript">
<syntaxhighlight lang="ecmascript">
var motion_timer_1_timeout_ms = 5 * 1000;
function makeMotionDetector(name, timeout_ms, detector_control, relay_control) {
var motion_timer_1_id = null;
  var motion_timer_id = null;
  defineRule(name, {
defineRule("motion_detector_1", {
      whenChanged: "wb-gpio/" + detector_control,
  whenChanged: "wb-gpio/A1_IN",
      then: function(newValue, devName, cellName) {
  then: function (newValue, devName, cellName) {
          if (!newValue) {
    var date = new Date();
              dev["wb-gpio/relay_control"] = true;
              if (motion_timer_id) {
                  clearTimeout(motion_timer_id);
              }


    <!--T:51-->
              <!--T:46-->
// time point marking the beginning of the interval
motion_timer_id = setTimeout(function() {
    // i.e. "today, at HH:MM". All dates are in UTC!
                  dev["wb-gpio/relay_control"] = false;
    var date_start = new Date(date);
                  motion_timer_id = null;
    date_start.setHours(9);
              }, timeout_ms);
    date_start.setMinutes(30);
          }
      }
  });
}
 
<!--T:47-->
makeMotionDetector("motion_detector_1", 20000, "EXT1_DR1", "EXT2_R3A1");
makeMotionDetector("motion_detector_2", 10000, "EXT1_DR2", "EXT2_R3A2");
makeMotionDetector("motion_detector_3", 10000, "EXT1_DR3", "EXT2_R3A3");
</syntaxhighlight>
 
== Активация правила только в определённое время == <!--T:48-->


    <!--T:52-->
<!--T:49-->
// time point marking the end of the interval
Правило как в предыдущем разделе, но выполняется только с 9:30 до 17:10 по UTC.
    var date_end = new Date(date);
    date_end.setHours(17);
    date_end.setMinutes(10);
   
    // if time is between 09:30 and 17:10 UTC
    if ((date > date_start) && (date < date_end)) {
      if (newValue) {
          dev["wb-gpio/EXT1_R3A1"] = 1;
 
          if (motion_timer_1_id) {
            clearTimeout(motion_timer_1_id);
        }
 
          motion_timer_1_id = setTimeout(function () {
            dev["wb-gpio/EXT1_R3A1"] = 0;           
            motion_timer_1_id = null;
          }, motion_timer_1_timeout_ms);             
      }
    }
  }
});
</syntaxhighlight>


== Роллеты == <!--T:53-->
<!--T:50-->
<syntaxhighlight lang="ecmascript">
var motion_timer_1_timeout_ms = 5 * 1000;
var motion_timer_1_id = null;
defineRule("motion_detector_1", {
  whenChanged: "wb-gpio/A1_IN",
  then: function (newValue, devName, cellName) {
    var date = new Date();


<!--T:54-->
    <!--T:51-->
Одно реле включает двигатель, поднимающий шторы, второе реле - включает двигатель, опускающий шторы.
// time point marking the beginning of the interval
Правило следит за тем, чтобы оба реле не были включены одновременно.
    // i.e. "today, at HH:MM". All dates are in UTC!
    var date_start = new Date(date);
    date_start.setHours(9);
    date_start.setMinutes(30);


<!--T:55-->
    <!--T:52-->
Кроме этого, правило отключает двигатели спустя заданное время после включения.
// time point marking the end of the interval
 
    var date_end = new Date(date);
<!--T:56-->
    date_end.setHours(17);
<syntaxhighlight lang="ecmascript">
    date_end.setMinutes(10);
(function() { //don't touch this line
   
 
    // if time is between 09:30 and 17:10 UTC
  var suffix = "1"; // must be different in different JS files
    if ((date > date_start) && (date < date_end)) {
      if (newValue) {
  var relay_up_device = "lc103_4";
          dev["wb-gpio/EXT1_R3A1"] = 1;
  var relay_up_control = "Relay 1";
 
          if (motion_timer_1_id) {
            clearTimeout(motion_timer_1_id);
        }
 
          motion_timer_1_id = setTimeout(function () {
            dev["wb-gpio/EXT1_R3A1"] = 0;           
            motion_timer_1_id = null;
          }, motion_timer_1_timeout_ms);             
      }
    }
  }
});
</syntaxhighlight>
 
== Роллеты == <!--T:53-->


  <!--T:57-->
<!--T:54-->
var relay_down_device = "lc103_4";
Одно реле включает двигатель, поднимающий шторы, второе реле - включает двигатель, опускающий шторы.
  var relay_down_control = "Relay 2";
Правило следит за тем, чтобы оба реле не были включены одновременно.


   <!--T:58-->
<!--T:55-->
var timeout_s = 15;
Кроме этого, правило отключает двигатели спустя заданное время после включения.
    
 
   // End of settings
<!--T:56-->
    
<syntaxhighlight lang="ecmascript">
(function() { //don't touch this line
 
  var suffix = "1"; // must be different in different JS files
  var relay_up_device = "lc103_4";
  var relay_up_control = "Relay 1";
 
  <!--T:57-->
var relay_down_device = "lc103_4";
  var relay_down_control = "Relay 2";
 
   <!--T:58-->
var timeout_s = 15;
    
   // End of settings
    
    
    
   var relay_up_timer_id = null;
   var relay_up_timer_id = null;
Строка 1117: Строка 1200:
<syntaxhighlight lang="bash">
<syntaxhighlight lang="bash">
{
{
"temperature_setpoint": 60,
"temperature_setpoint": 25,
"humidity_setpoint": 14
"humidity_setpoint": 14
}
}
Строка 1620: Строка 1703:
     }
     }
});
});
</syntaxhighlight>
==Работа с последовательным портом через RPC==
[[File:mqtt-rpc.png|300px|thumb|right|Работа с последовательным портом через RPC]]
Если устройство на шине работает по протоколу, который не поддерживается драйвером [[Wb-mqtt-serial_driver |wb-mqtt-serial]] можно формировать запросы вручную и отправлять их драйверу через [https://github.com/wirenboard/mqtt-rpc RPC-MQTT].
RPC-MQTT создает MQTT-топик для отправки запросов, и топик для чтения ответов от драйвера. Поэтому для его использования достаточно отправить запрос в нужный топик функцией <code>publish()</code> и прочитать ответ функцией <code>trackMqtt()</code>. Как узнать адреса топиков описано в [https://github.com/wirenboard/mqtt-rpc документации].
В примере написан скрипт на wb-rules для отправки Modbus-запроса устройству Wiren Board на шине RS-485.
Переменная <code>message</code> содержит Modbus-запрос, сформированный в соответствии со стандартом [[Modbus |Modbus RTU]].
Переменная <code>pathRPC</code> — это адрес MQTT-топика, в который отправляются запросы для драйвера wb-mqtt-serial. Для каждого сервиса используется свой топик, и узнать его адрес можно из документации на RPC-MQTT.
<syntaxhighlight lang="bash">
var pathRPC = "/rpc/v1/wb-mqtt-serial/port/Load/";  //Адрес топика в который отправляется запрос
var modbusPort = "/dev/ttyRS485-1";
var modbusSpeed = 9600;
var modbusParity = "N";
var modbusStopbit = 2;
var message = "E0300C8000644C9";


var clientID = "testRPC";
function requestRPC(modbusPort, modbusSpeed, modbusParity, modbusStopbit, clientID, requiestID, messageType, message, responseSize){
var strJson = JSON.stringify({params: {response_size: responseSize, format: messageType, path: modbusPort, baud_rate: modbusSpeed, parity: modbusParity, "data_bits" : 8, "stop_bits" : modbusStopbit, "msg": message}, "id" : requiestID});
  log.info("strJson =", strJson);
  publish(pathRPC+clientID, strJson, 2, false)
};
trackMqtt(pathRPC+clientID+"/reply", function(message){
  log.info("name: {}, value: {}".format(message.topic, message.value))
});
requestRPC(modbusPort, modbusSpeed, modbusParity, modbusStopbit, clientID, 1, "HEX", message, 8)
</syntaxhighlight>
Если запрос отправлен без ошибок, то в лог будет выведено сообщение вида:
<syntaxhighlight lang="bash">
name: /rpc/v1/wb-mqtt-serial/port/Load/testRPC/reply,
value: {"error":null,"id":1,"result":{"response":"0e030400002569df"}}
</syntaxhighlight>
== Получение SMS ==
В примере с периодом в 1 секунду выводится в лог вся информация о последнем сообщении. Полученные SMS будут в capturedOutput. Пример из [https://support.wirenboard.com/t/wb7-modem-rabota-s-sms-soobshheniyami/18159 темы на портале].
<syntaxhighlight lang="bash">
var period = 1000;
setInterval(function() {
    runShellCommand("mmcli --modem wbc --messaging-list-sms --output-keyvalue | grep length | cut -f2 -d':'", {
        captureOutput: true,
        exitCallback: function(exitCode, capturedOutput) {
            if (exitCode === 0) {
                runShellCommand("mmcli --modem wbc --sms " + (parseInt(capturedOutput) - 1).toString(), {
                    captureOutput: true,
                    exitCallback: function(exitCode, capturedOutput) {
                        if (exitCode === 0) {
                            log(capturedOutput);
                            return;
                        }
                    }
                });
                return;
            }
        }
    });
}, period);
</syntaxhighlight>
</syntaxhighlight>