Rule Examples/en: различия между версиями
FuzzyBot (обсуждение | вклад) (Обновление для соответствия новой версии исходной страницы.) |
Matveevrj (обсуждение | вклад) (Новая страница: «When the CO2 concentration is over 1001, we flash red once a second. <div class="mw-collapsible mw-collapsed"; style="width:600px; overflow: hidden;"> <syntaxhighlight lang="ecmascript"> defineRule("msw3_co2", { whenChanged: "wb-msw-v3_97/CO2", then: function(newValue, devName, cellName) { var co2_good = newValue < 650; var co2_middle = newValue < 1000 && newValue > 651; var co2_bad = newValue > 1001;») |
||
(не показаны 62 промежуточные версии 2 участников) | |||
Строка 1: | Строка 1: | ||
{{DISPLAYTITLE: Примеры правил}} | |||
<languages/> | <languages/> | ||
=== Control tracking === | === Control tracking === | ||
Строка 8: | Строка 7: | ||
For example, a rule can turn on a siren and a lamp if a motion sensor detects movement. | For example, a rule can turn on a siren and a lamp if a motion sensor detects movement. | ||
In the example, the motion sensor is connected to the | In the example, the motion sensor is connected to the "dry contact" input, control type "switch". The siren is connected to the built-in Wiren Board relay, and the lamp is connected to the relay box via Modbus. When the "dry contact" input (motion sensor output) is closed, "1" is supplied to the lamp and the relay, and "0" when it is off. | ||
The rule is triggered every time the control value | The rule is triggered every time the control value "D1_IN" of the device "wb-gpio" is changed. The new value of this control is passed to the rule code as a variable newValue. | ||
<syntaxhighlight lang="ecmascript"> | <syntaxhighlight lang="ecmascript"> | ||
Строка 17: | Строка 16: | ||
whenChanged: "wb-gpio/D1_IN", | whenChanged: "wb-gpio/D1_IN", | ||
then: function (newValue, devName, cellName) { | then: function (newValue, devName, cellName) { | ||
dev["wb-gpio | dev["wb-gpio"]["Relay_2"] = newValue; | ||
dev["wb-mrm2_6 | dev["wb-mrm2_6"]["Relay 1"] = newValue; | ||
} | } | ||
Строка 41: | Строка 40: | ||
whenChanged: "simple_test/enabled", | whenChanged: "simple_test/enabled", | ||
then: function (newValue, devName, cellName) { | then: function (newValue, devName, cellName) { | ||
dev["wb-gpio | dev["wb-gpio"]["Relay_2"] = newValue; | ||
dev["wb-mrm2_6 | dev["wb-mrm2_6"]["Relay 1"] = newValue; | ||
} | } | ||
Строка 50: | Строка 49: | ||
=== Motion detection with timeout === | === Motion detection with timeout === | ||
A motion detector with a | A motion detector with a "dry contact" output is connected to input D2. When motion is detected, it shorts D2 and GND and status "1" appears on the corresponding <code>wb-gpio/D2_IN</code> channel. | ||
The lighting is connected via a built-in relay corresponding to the <code>wb-gpio/Relay_1</code> channel. | The lighting is connected via a built-in relay corresponding to the <code>wb-gpio/Relay_1</code> channel. | ||
The rule works like this: | The rule works like this: | ||
* when movement appears, the light turns on. If a thirty-second | * when movement appears, the light turns on. If a thirty-second "off" timer was previously started, this timer is disabled; | ||
* when motion is lost, a thirty second | * when motion is lost, a thirty second "off" timer is started. If he manages to reach the end, the light turns off. | ||
<syntaxhighlight lang="ecmascript"> | <syntaxhighlight lang="ecmascript"> | ||
Строка 66: | Строка 65: | ||
then: function (newValue, devName, cellName) { | then: function (newValue, devName, cellName) { | ||
if (newValue) { | if (newValue) { | ||
dev["wb-gpio | dev["wb-gpio"]["Relay_1"] = true; | ||
if (motion_timer_1_id) { | if (motion_timer_1_id) { | ||
clearTimeout(motion_timer_1_id); | clearTimeout(motion_timer_1_id); | ||
} | } | ||
motion_timer_1_id = setTimeout(function () { | motion_timer_1_id = setTimeout(function () { | ||
dev["wb-gpio | dev["wb-gpio"]["Relay_1"] = false; | ||
motion_timer_1_id = null; | motion_timer_1_id = null; | ||
}, motion_timer_1_timeout_ms); | }, motion_timer_1_timeout_ms); | ||
Строка 90: | Строка 89: | ||
then: function(newValue, devName, cellName) { | then: function(newValue, devName, cellName) { | ||
if (!newValue) { | if (!newValue) { | ||
dev["wb-gpio | dev["wb-gpio"][relay_control] = true; | ||
if (motion_timer_id) { | if (motion_timer_id) { | ||
clearTimeout(motion_timer_id); | clearTimeout(motion_timer_id); | ||
Строка 96: | Строка 95: | ||
motion_timer_id = setTimeout(function() { | motion_timer_id = setTimeout(function() { | ||
dev["wb-gpio | dev["wb-gpio"][relay_control] = false; | ||
motion_timer_id = null; | motion_timer_id = null; | ||
}, timeout_ms); | }, timeout_ms); | ||
Строка 136: | Строка 135: | ||
if ((date > date_start) && (date < date_end)) { | if ((date > date_start) && (date < date_end)) { | ||
if (newValue) { | if (newValue) { | ||
dev["wb-gpio | dev["wb-gpio"]["EXT1_R3A1"] = 1; | ||
if (motion_timer_1_id) { | if (motion_timer_1_id) { | ||
Строка 143: | Строка 142: | ||
motion_timer_1_id = setTimeout(function () { | motion_timer_1_id = setTimeout(function () { | ||
dev["wb-gpio | dev["wb-gpio"]["EXT1_R3A1"] = 0; | ||
motion_timer_1_id = null; | motion_timer_1_id = null; | ||
}, motion_timer_1_timeout_ms); | }, motion_timer_1_timeout_ms); | ||
Строка 180: | Строка 179: | ||
defineRule( "roller_shutter_up_on" + suffix, { | defineRule( "roller_shutter_up_on" + suffix, { | ||
asSoonAs: function() { | asSoonAs: function() { | ||
return dev[ | return dev[relay_up_device][relay_up_control]; | ||
}, | }, | ||
then: function () { | then: function () { | ||
Строка 188: | Строка 187: | ||
relay_up_timer_id = setTimeout(function() { | relay_up_timer_id = setTimeout(function() { | ||
return dev[ | return dev[relay_up_device][relay_up_control] = 0; | ||
}, timeout_s * 1000); | }, timeout_s * 1000); | ||
} | } | ||
Строка 203: | Строка 202: | ||
relay_down_timer_id = setTimeout(function() { | relay_down_timer_id = setTimeout(function() { | ||
dev[ | dev[relay_down_device][relay_down_control] = 0; | ||
}, timeout_s * 1000); | }, timeout_s * 1000); | ||
} | } | ||
Строка 210: | Строка 209: | ||
defineRule("roller_shutter_both_on" + suffix, { | defineRule("roller_shutter_both_on" + suffix, { | ||
asSoonAs: function() { | asSoonAs: function() { | ||
return dev[relay_up_device][relay_up_control] && dev[ | return dev[relay_up_device][relay_up_control] && dev[relay_down_device][relay_down_control]; | ||
}, | }, | ||
then: function () { | then: function () { | ||
Строка 223: | Строка 222: | ||
dev[relay_up_device][relay_up_control] = 0; | dev[relay_up_device][relay_up_control] = 0; | ||
dev[ | dev[relay_down_device][relay_down_control] = 0; | ||
log("Both roller shutter relays on, switching them off"); | log("Both roller shutter relays on, switching them off"); | ||
} | } | ||
Строка 297: | Строка 296: | ||
then: function(newValue, devName, cellName) { | then: function(newValue, devName, cellName) { | ||
if(newValue){ | if(newValue){ | ||
dev["water_meters | dev["water_meters"]["water_meter_1"] = ((parseInt(newValue) - counterCorrection) * inpulseValue) + meterCorrection; // We multiply the value of the counter by the number of liters / pulse and add the correction value. | ||
} | } | ||
} | } | ||
Строка 412: | Строка 411: | ||
== Sensor MSW v.3 == | == Sensor MSW v.3 == | ||
When connecting the WB-MSW v.3 sensor to the Wiren Board controller, it is possible to create interesting scenarios using data from the sensor. For example, turn on the light when moving, signal with LEDs when the CO2 or VOC value is exceeded, turn on the air conditioner if it is hot or the air humidifier if the air is too dry. Rules are created individually for tasks. Here we will give some examples to understand the principle of working with the sensor. More examples of writing rules can be found in the '''[[ | When connecting the WB-MSW v.3 sensor to the Wiren Board controller, it is possible to create interesting scenarios using data from the sensor. For example, turn on the light when moving, signal with LEDs when the CO2 or VOC value is exceeded, turn on the air conditioner if it is hot or the air humidifier if the air is too dry. Rules are created individually for tasks. Here we will give some examples to understand the principle of working with the sensor. More examples of writing rules can be found in the '''[[Wb-rules rule engine]]''' documentation. | ||
=== CO2 === | === CO2 === | ||
Строка 430: | Строка 429: | ||
if (co2_good) { | if (co2_good) { | ||
dev[" | dev[devName]["Green LED"] = true; | ||
dev[" | dev[devName]["Red LED"] = false; | ||
dev[" | dev[devName]["LED Period (s)"] = 10; | ||
} | } | ||
if (co2_middle) { | if (co2_middle) { | ||
dev[" | dev[devName]["Green LED"] = true; | ||
dev[" | dev[devName]["Red LED"] = true; | ||
dev[" | dev[devName]["LED Period (s)"] = 5; | ||
} | } | ||
if (co2_bad) { | if (co2_bad) { | ||
dev[" | dev[devName]["Green LED"] = false; | ||
dev[" | dev[devName]["Red LED"] = true; | ||
dev[" | dev[devName]["LED Period (s)"] = 1; | ||
} | } | ||
} | } | ||
Строка 458: | Строка 457: | ||
then: function(newValue, devName, cellName) { | then: function(newValue, devName, cellName) { | ||
if (newValue > 50) { | if (newValue > 50) { | ||
if (dev["wb-msw-v3_97 | if (dev["wb-msw-v3_97"]["Illuminance"] < 50) { | ||
dev["wb-mr3_11 | dev["wb-mr3_11"]["K1"] = true; | ||
} | } | ||
} else { | } else { | ||
dev["wb-mr3_11 | dev["wb-mr3_11"]["K1"] = false; | ||
} | } | ||
} | } | ||
Строка 472: | Строка 471: | ||
=== System rules === | === System rules === | ||
Many of the indications that are visible in the web interface of the controller out of the box are also created by rules on the wb-rules engine. Their code is here: [https://github.com/wirenboard/wb-rules-system https://github.com/wirenboard/wb-rules-system]. The system rules are collected in the <code>wb-rules-system</code> package, the script files on the controller are located in the <code>/usr/share/wb-rules-system/</code> folder. | Many of the indications that are visible in the web interface of the controller out of the box are also created by rules on the wb-rules engine. Their code is here: [https://github.com/wirenboard/wb-rules-system https://github.com/wirenboard/wb-rules-system]. The system rules are collected in the <code>wb-rules-system</code> package, the script files on the controller are located in the <code>/usr/share/wb-rules-system/</code> folder.<code></code> | ||
<div lang="ru" dir="ltr" class="mw-content-ltr"> | |||
Несколько примеров системных правил ниже. | |||
</div> | |||
=== | <div lang="ru" dir="ltr" class="mw-content-ltr"> | ||
=== Правило для пищалки === | |||
</div> | |||
[https://github.com/contactless/wb-rules-system/blob/master/rules/buzzer.js | <div lang="ru" dir="ltr" class="mw-content-ltr"> | ||
[https://github.com/contactless/wb-rules-system/blob/master/rules/buzzer.js Правило] создаёт виртуальное устройство buzzer с ползунками для регулировки громкости и частоты, а также кнопкой включения звука. | |||
</div> | |||
<div lang="ru" dir="ltr" class="mw-content-ltr"> | |||
<syntaxhighlight lang="ecmascript"> | <syntaxhighlight lang="ecmascript"> | ||
defineVirtualDevice("buzzer", { | defineVirtualDevice("buzzer", { | ||
title: "Buzzer", // | title: "Buzzer", // | ||
</div> | |||
cells: { | <div lang="ru" dir="ltr" class="mw-content-ltr"> | ||
cells: { | |||
frequency : { | frequency : { | ||
type : "range", | type : "range", | ||
Строка 501: | Строка 509: | ||
} | } | ||
}); | }); | ||
</div> | |||
<div lang="ru" dir="ltr" class="mw-content-ltr"> | |||
// setup pwm2 | // setup pwm2 | ||
runShellCommand("echo 2 > /sys/class/pwm/pwmchip0/export"); | runShellCommand("echo 2 > /sys/class/pwm/pwmchip0/export"); | ||
</div> | |||
<div lang="ru" dir="ltr" class="mw-content-ltr"> | |||
function _buzzer_set_params() { | function _buzzer_set_params() { | ||
var period = parseInt(1.0 / dev.buzzer.frequency * 1E9); | var period = parseInt(1.0 / dev.buzzer.frequency * 1E9); | ||
var duty_cycle = parseInt(dev.buzzer.volume * 1.0 / 100 * period * 0.5); | var duty_cycle = parseInt(dev.buzzer.volume * 1.0 / 100 * period * 0.5); | ||
</div> | |||
runShellCommand("echo " + period + " > /sys/class/pwm/pwmchip0/pwm2/period"); | <div lang="ru" dir="ltr" class="mw-content-ltr"> | ||
runShellCommand("echo " + period + " > /sys/class/pwm/pwmchip0/pwm2/period"); | |||
runShellCommand("echo " + duty_cycle + " > /sys/class/pwm/pwmchip0/pwm2/duty_cycle"); | runShellCommand("echo " + duty_cycle + " > /sys/class/pwm/pwmchip0/pwm2/duty_cycle"); | ||
}; | }; | ||
</div> | |||
<div lang="ru" dir="ltr" class="mw-content-ltr"> | |||
defineRule("_system_buzzer_params", { | defineRule("_system_buzzer_params", { | ||
whenChanged: [ | whenChanged: [ | ||
Строка 521: | Строка 537: | ||
"buzzer/volume", | "buzzer/volume", | ||
], | ], | ||
</div> | |||
then: function (newValue, devName, cellName) { | <div lang="ru" dir="ltr" class="mw-content-ltr"> | ||
then: function (newValue, devName, cellName) { | |||
if ( dev.buzzer.enabled) { | if ( dev.buzzer.enabled) { | ||
_buzzer_set_params(); | _buzzer_set_params(); | ||
Строка 528: | Строка 546: | ||
} | } | ||
}); | }); | ||
</div> | |||
<div lang="ru" dir="ltr" class="mw-content-ltr"> | |||
defineRule("_system_buzzer_onof", { | defineRule("_system_buzzer_onof", { | ||
whenChanged: "buzzer/enabled", | whenChanged: "buzzer/enabled", | ||
Строка 541: | Строка 561: | ||
} | } | ||
}); | }); | ||
</div> | |||
<div lang="ru" dir="ltr" class="mw-content-ltr"> | |||
</syntaxhighlight> | </syntaxhighlight> | ||
</div> | |||
=== | <div lang="ru" dir="ltr" class="mw-content-ltr"> | ||
=== Правило для статуса питания === | |||
</div> | |||
[https://github.com/contactless/wb-rules-system/blob/master/rules/power_status.js | <div lang="ru" dir="ltr" class="mw-content-ltr"> | ||
[https://github.com/contactless/wb-rules-system/blob/master/rules/power_status.js Правило] создаёт виртуальное устройство, которое сообщает текущий статус питания. В качестве входных данных используется два канала АЦП: измерение напряжения на аккумуляторе и измерение входного напряжения. | |||
</div> | |||
<div lang="ru" dir="ltr" class="mw-content-ltr"> | |||
Реализована следующая логика: | |||
</div> | |||
1. | <div lang="ru" dir="ltr" class="mw-content-ltr"> | ||
1. Если входное напряжение меньше напряжение на аккумуляторе, то значит плата питается от аккумулятора. В этом случае, также отображается 0V в качестве входного напряжения. | |||
</div> | |||
2. | <div lang="ru" dir="ltr" class="mw-content-ltr"> | ||
2. Если входное напряжение больше напряжения на аккумуляторе, то плата работает от внешнего источника питания. В качестве входонго напряжения отображается измерение с канала Vin. | |||
</div> | |||
<div lang="ru" dir="ltr" class="mw-content-ltr"> | |||
Для иллюстрации правила используют два разных способа срабатывания: по изменению значения контрола (правило _system_track_vin) и по изменению значения выражения (два других). | |||
</div> | |||
<div lang="ru" dir="ltr" class="mw-content-ltr"> | |||
<syntaxhighlight lang="ecmascript"> | <syntaxhighlight lang="ecmascript"> | ||
</div> | |||
<div lang="ru" dir="ltr" class="mw-content-ltr"> | |||
defineVirtualDevice("power_status", { | defineVirtualDevice("power_status", { | ||
title: "Power status", // | title: "Power status", // | ||
</div> | |||
cells: { | <div lang="ru" dir="ltr" class="mw-content-ltr"> | ||
cells: { | |||
'working on battery' : { | 'working on battery' : { | ||
type : "switch", | type : "switch", | ||
Строка 572: | Строка 612: | ||
value : 0 | value : 0 | ||
} | } | ||
</div> | |||
} | <div lang="ru" dir="ltr" class="mw-content-ltr"> | ||
} | |||
}); | }); | ||
</div> | |||
<div lang="ru" dir="ltr" class="mw-content-ltr"> | |||
defineRule("_system_track_vin", { | defineRule("_system_track_vin", { | ||
whenChanged: "wb-adc/Vin", | whenChanged: "wb-adc/Vin", | ||
then: function() { | then: function() { | ||
if (dev["wb-adc | if (dev["wb-adc"]["Vin"] < dev["wb-adc"]["BAT"] ) { | ||
dev["power_status | dev["power_status"]["Vin"] = 0; | ||
} else { | } else { | ||
dev["power_status | dev["power_status"]["Vin"] = dev["wb-adc"]["Vin"] ; | ||
} | } | ||
} | } | ||
}); | }); | ||
</div> | |||
<div lang="ru" dir="ltr" class="mw-content-ltr"> | |||
defineRule("_system_dc_on", { | defineRule("_system_dc_on", { | ||
asSoonAs: function () { | asSoonAs: function () { | ||
return dev["wb-adc | return dev["wb-adc"]["Vin"] > dev["wb-adc"]["BAT"]; | ||
}, | }, | ||
then: function () { | then: function () { | ||
dev["power_status | dev["power_status"]["working on battery"] = false; | ||
} | } | ||
}); | }); | ||
</div> | |||
<div lang="ru" dir="ltr" class="mw-content-ltr"> | |||
defineRule("_system_dc_off", { | defineRule("_system_dc_off", { | ||
asSoonAs: function () { | asSoonAs: function () { | ||
return dev["wb-adc | return dev["wb-adc"]["Vin"] <= dev["wb-adc"]["BAT"]; | ||
}, | }, | ||
then: function () { | then: function () { | ||
dev["power_status | dev["power_status"]["working on battery"] = true; | ||
} | } | ||
}); | }); | ||
</div> | |||
<div lang="ru" dir="ltr" class="mw-content-ltr"> | |||
</syntaxhighlight> | </syntaxhighlight> | ||
</div> | |||
== | <div lang="ru" dir="ltr" class="mw-content-ltr"> | ||
== Термостат == | |||
Пример простого термостата из [https://support.wirenboard.com/t/novaya-versiya-dvizhka-pravil/4196/158 темы на портале поддержки]. | |||
</div> | |||
<div lang="ru" dir="ltr" class="mw-content-ltr"> | |||
<syntaxhighlight lang="js"> | <syntaxhighlight lang="js"> | ||
defineVirtualDevice("Termostat", { | defineVirtualDevice("Termostat", { | ||
title: "Termostat", | title: "Termostat", | ||
cells: { | cells: { | ||
// =============== | // =============== Прихожая теплый пол | ||
"R01-TS16-1-mode": {// | "R01-TS16-1-mode": {//режим 0-ручной 1-по расписанию | ||
type: "switch", | type: "switch", | ||
value: false, | value: false, | ||
}, | }, | ||
"R01-TS16-1-setpoint": {// | "R01-TS16-1-setpoint": {//уставка | ||
type: "range", | type: "range", | ||
value: 25, | value: 25, | ||
Строка 630: | Строка 684: | ||
readonly: false | readonly: false | ||
}, | }, | ||
"R01-TS16-1-lock": {// | "R01-TS16-1-lock": {//блокировка в визуализации 0-снята 1-заблокирована | ||
type: "switch", | type: "switch", | ||
value: false, | value: false, | ||
},....... | },....... | ||
</div> | |||
<div lang="ru" dir="ltr" class="mw-content-ltr"> | |||
var hysteresis = 0.5; | var hysteresis = 0.5; | ||
function Termostat(name, temp, setpoint, TS, TS_onoff) { | function Termostat(name, temp, setpoint, TS, TS_onoff) { | ||
defineRule(name, { | defineRule(name, { | ||
whenChanged: temp, // | whenChanged: temp, //при изменении состояния датчика | ||
then: function (newValue, devName, cellName) { // | then: function (newValue, devName, cellName) { //выполняй следующие действия | ||
if (dev[TS_onoff]) { | if (dev[TS_onoff]) { | ||
if ( newValue < dev[setpoint] - hysteresis) { //если температура датчика меньше уставки - гистерезис | |||
dev[TS] = true; | |||
} | |||
if ( newValue > dev[setpoint] + hysteresis) { //если температура датчика больше виртуальной уставки + гистерезис | |||
dev[TS] = false; | |||
} | |||
} | } | ||
else dev[TS] = false; | else dev[TS] = false; | ||
Строка 652: | Строка 708: | ||
}); | }); | ||
} | } | ||
</div> | |||
Termostat("R01-TS16-1", "A60-M1W3/External Sensor 1", "Termostat/R01-TS16-1-setpoint", "wb-gpio/EXT4_R3A1", "Termostat/R01-TS16-1-onoff"); // | <div lang="ru" dir="ltr" class="mw-content-ltr"> | ||
Termostat("R01-TS16-1", "A60-M1W3/External Sensor 1", "Termostat/R01-TS16-1-setpoint", "wb-gpio/EXT4_R3A1", "Termostat/R01-TS16-1-onoff"); // Прихожая теплый пол | |||
</div> | |||
<div lang="ru" dir="ltr" class="mw-content-ltr"> | |||
</syntaxhighlight> | </syntaxhighlight> | ||
== | == Отправка команд по RS-485 == | ||
</div> | |||
<div lang="ru" dir="ltr" class="mw-content-ltr"> | |||
Для примера отправим команду устройству на порт ''/dev/ttyNSC0'' (соответствует аппаратному порту RS-485-ISO на [[Special:MyLanguage/Wiren Board 4|Wiren Board 4]]). | |||
Для этого будем использовать движок правил и возможность выполнения произвольных shell-команд. Подробнее см. [https://github.com/contactless/wb-rules#%D0%94%D1%80%D1%83%D0%B3%D0%B8%D0%B5-%D0%BF%D1%80%D0%B5%D0%B4%D0%BE%D0%BF%D1%80%D0%B5%D0%B4%D0%B5%D0%BB%D1%91%D0%BD%D0%BD%D1%8B%D0%B5-%D1%84%D1%83%D0%BD%D0%BA%D1%86%D0%B8%D0%B8-%D0%B8-%D0%BF%D0%B5%D1%80%D0%B5%D0%BC%D0%B5%D0%BD%D0%BD%D1%8B%D0%B5 в документации]. | |||
</div> | |||
<div lang="ru" dir="ltr" class="mw-content-ltr"> | |||
С помощью движка правил создадим виртуальное устройство с контролом типа switch (переключатель). | |||
</div> | |||
<div lang="ru" dir="ltr" class="mw-content-ltr"> | |||
При включении переключателя будем отправлять команду (уст. Яркость кан. 00=0xff) для Uniel UCH-M141: | |||
<pre> | <pre> | ||
FF FF 0A 01 FF 00 00 0A | FF FF 0A 01 FF 00 00 0A | ||
</pre> | </pre> | ||
</div> | |||
<div lang="ru" dir="ltr" class="mw-content-ltr"> | |||
При выключении переключателя будем отправлять команду (уст. Яркость кан. 00=0x00) для Uniel UCH-M141: | |||
<pre> | <pre> | ||
FF FF 0A 01 00 00 00 0B | FF FF 0A 01 00 00 00 0B | ||
</pre> | </pre> | ||
</div> | |||
1. | <div lang="ru" dir="ltr" class="mw-content-ltr"> | ||
1. Настройка порта | |||
</div> | |||
<div lang="ru" dir="ltr" class="mw-content-ltr"> | |||
Для настройки порта /dev/ttyNSC0 на скорость 9600 надо выполнить следующую команду | |||
</div> | |||
<div lang="ru" dir="ltr" class="mw-content-ltr"> | |||
<pre> | <pre> | ||
stty -F /dev/ttyNSC0 ospeed 9600 ispeed 9600 raw clocal -crtscts -parenb -echo cs8 | stty -F /dev/ttyNSC0 ospeed 9600 ispeed 9600 raw clocal -crtscts -parenb -echo cs8 | ||
</pre> | </pre> | ||
</div> | |||
2. | <div lang="ru" dir="ltr" class="mw-content-ltr"> | ||
2. Отправка команды | |||
</div> | |||
<div lang="ru" dir="ltr" class="mw-content-ltr"> | |||
Отправка данных делается следующей шелл-командой: | |||
</div> | |||
<div lang="ru" dir="ltr" class="mw-content-ltr"> | |||
<pre> | <pre> | ||
/usr/bin/printf '\xFF\xFF\x0A\x01\xD1\x06\x00\xE2' >/dev/ttyNSC0 | /usr/bin/printf '\xFF\xFF\x0A\x01\xD1\x06\x00\xE2' >/dev/ttyNSC0 | ||
</pre> | </pre> | ||
где "\xFF\xFF\x0A\x01\xD1\x06\x00\xE2" - это запись команды "FF FF 0A 01 D1 06 00 E2". | |||
</div> | |||
3. | <div lang="ru" dir="ltr" class="mw-content-ltr"> | ||
3. Создадим в движке правил новый файл с правилами <code>/etc/wb-rules/rs485_cmd.js</code> | |||
</div> | |||
<div lang="ru" dir="ltr" class="mw-content-ltr"> | |||
Файл можно редактировать с помощью vim, nano или mcedit в сеансе ssh на устройстве, либо залить его с помощью SCP. | |||
</div> | |||
<div lang="ru" dir="ltr" class="mw-content-ltr"> | |||
<pre> | <pre> | ||
root@wirenboard:~# mcedit /etc/wb-rules/rs485_cmd.js | root@wirenboard:~# mcedit /etc/wb-rules/rs485_cmd.js | ||
</pre> | </pre> | ||
</div> | |||
4. | <div lang="ru" dir="ltr" class="mw-content-ltr"> | ||
4. Описываем в файле виртуальный девайс | |||
</div> | |||
<div lang="ru" dir="ltr" class="mw-content-ltr"> | |||
<syntaxhighlight lang="ecmascript"> | <syntaxhighlight lang="ecmascript"> | ||
defineVirtualDevice("rs485_cmd", { | defineVirtualDevice("rs485_cmd", { | ||
Строка 714: | Строка 804: | ||
}); | }); | ||
</syntaxhighlight> | </syntaxhighlight> | ||
</div> | |||
5. | <div lang="ru" dir="ltr" class="mw-content-ltr"> | ||
5. Перезапускаем wb-rules и проверяем работу | |||
</div> | |||
<div lang="ru" dir="ltr" class="mw-content-ltr"> | |||
<pre> | <pre> | ||
root@wirenboard:~# /etc/init.d/wb-rules restart | root@wirenboard:~# /etc/init.d/wb-rules restart | ||
root@wirenboard:~# tail -f /var/log/messages | root@wirenboard:~# tail -f /var/log/messages | ||
</pre> | </pre> | ||
</div> | |||
<div lang="ru" dir="ltr" class="mw-content-ltr"> | |||
В логе не должно быть сообщений об ошибке (выход через control-c) | |||
</div> | |||
<div lang="ru" dir="ltr" class="mw-content-ltr"> | |||
В веб-интерфейсе в разделе Devices должно появиться новое устройство "Send custom command to RS-485 port". | |||
</div> | |||
6. | <div lang="ru" dir="ltr" class="mw-content-ltr"> | ||
6. Добавим функцию для конфигурирования порта. | |||
</div> | |||
<div lang="ru" dir="ltr" class="mw-content-ltr"> | |||
<syntaxhighlight lang="ecmascript"> | <syntaxhighlight lang="ecmascript"> | ||
function setup_port() { | function setup_port() { | ||
runShellCommand("stty -F /dev/ttyNSC0 ospeed 9600 ispeed 9600 raw clocal -crtscts -parenb -echo cs8"); | runShellCommand("stty -F /dev/ttyNSC0 ospeed 9600 ispeed 9600 raw clocal -crtscts -parenb -echo cs8"); | ||
} | } | ||
</div> | |||
<div lang="ru" dir="ltr" class="mw-content-ltr"> | |||
</syntaxhighlight> | </syntaxhighlight> | ||
</div> | |||
7. | <div lang="ru" dir="ltr" class="mw-content-ltr"> | ||
7. Опишем правила на включение и выключение переключателя | |||
</div> | |||
<div lang="ru" dir="ltr" class="mw-content-ltr"> | |||
<syntaxhighlight lang="ecmascript"> | <syntaxhighlight lang="ecmascript"> | ||
defineRule("_rs485_switch_on", { | defineRule("_rs485_switch_on", { | ||
Строка 746: | Строка 854: | ||
} | } | ||
}); | }); | ||
</div> | |||
<div lang="ru" dir="ltr" class="mw-content-ltr"> | |||
defineRule("_rs485_switch_off", { | defineRule("_rs485_switch_off", { | ||
asSoonAs: function () { | asSoonAs: function () { | ||
Строка 755: | Строка 865: | ||
} | } | ||
}); | }); | ||
</div> | |||
<div lang="ru" dir="ltr" class="mw-content-ltr"> | |||
</syntaxhighlight> | </syntaxhighlight> | ||
</div> | |||
<div lang="ru" dir="ltr" class="mw-content-ltr"> | |||
Обратите внимание на двойное экранирование. | |||
</div> | |||
7. | <div lang="ru" dir="ltr" class="mw-content-ltr"> | ||
7. Собираем всё вместе | |||
</div> | |||
<div lang="ru" dir="ltr" class="mw-content-ltr"> | |||
Полное содержимое файла с правилами: | |||
</div> | |||
<div lang="ru" dir="ltr" class="mw-content-ltr"> | |||
<syntaxhighlight lang="ecmascript"> | <syntaxhighlight lang="ecmascript"> | ||
defineVirtualDevice("rs485_cmd", { | defineVirtualDevice("rs485_cmd", { | ||
Строка 774: | Строка 894: | ||
} | } | ||
}); | }); | ||
</div> | |||
<div lang="ru" dir="ltr" class="mw-content-ltr"> | |||
function setup_port() { | function setup_port() { | ||
runShellCommand("stty -F /dev/ttyNSC0 ospeed 9600 ispeed 9600 raw clocal -crtscts -parenb -echo cs8"); | runShellCommand("stty -F /dev/ttyNSC0 ospeed 9600 ispeed 9600 raw clocal -crtscts -parenb -echo cs8"); | ||
} | } | ||
</div> | |||
<div lang="ru" dir="ltr" class="mw-content-ltr"> | |||
defineRule("_rs485_switch_on", { | defineRule("_rs485_switch_on", { | ||
asSoonAs: function () { | asSoonAs: function () { | ||
Строка 787: | Строка 911: | ||
} | } | ||
}); | }); | ||
</div> | |||
<div lang="ru" dir="ltr" class="mw-content-ltr"> | |||
defineRule("_rs485_switch_off", { | defineRule("_rs485_switch_off", { | ||
asSoonAs: function () { | asSoonAs: function () { | ||
Строка 796: | Строка 922: | ||
} | } | ||
}); | }); | ||
</div> | |||
setTimeout(setup_port, 1000); // | <div lang="ru" dir="ltr" class="mw-content-ltr"> | ||
setTimeout(setup_port, 1000); // запланировать выполнение setup_port() через 1 секунду после старта правил. | |||
</div> | |||
<div lang="ru" dir="ltr" class="mw-content-ltr"> | |||
</syntaxhighlight> | </syntaxhighlight> | ||
</div> | |||
== | <div lang="ru" dir="ltr" class="mw-content-ltr"> | ||
== Отправка сообщения через Telegram-бота == | |||
{{Anchor|telegram}} | {{Anchor|telegram}} | ||
Сообщения отправляются с использованием [https://core.telegram.org/api#telegram-api Telegram API] через <code>curl</code>. | |||
</div> | |||
<div lang="ru" dir="ltr" class="mw-content-ltr"> | |||
<syntaxhighlight lang="ecmascript"> | <syntaxhighlight lang="ecmascript"> | ||
var message = "Text"; // напишите свой текст сообщения | |||
var token = "TOKEN"; // | var token = "TOKEN"; // замените на токен бота | ||
var chat_id = CHATID; // | var chat_id = CHATID; // замените на свой chat_id | ||
var command = 'curl -s -X POST https://api.telegram.org/bot{}/sendMessage -d chat_id={} -d text="{}"'.format(token, chat_id, message); | var command = 'curl -s -X POST https://api.telegram.org/bot{}/sendMessage -d chat_id={} -d text="{}"'.format(token, chat_id, message); | ||
</div> | |||
<div lang="ru" dir="ltr" class="mw-content-ltr"> | |||
runShellCommand(command); | runShellCommand(command); | ||
</syntaxhighlight> | </syntaxhighlight> | ||
</div> | |||
== | <div lang="ru" dir="ltr" class="mw-content-ltr"> | ||
== Обработка ошибок в работе с serial-устройствами == | |||
Реализована через подписку на все топики '''meta/error'''. | |||
</div> | |||
<div lang="ru" dir="ltr" class="mw-content-ltr"> | |||
<syntaxhighlight lang="ecmascript"> | <syntaxhighlight lang="ecmascript"> | ||
defineVirtualDevice("meta_error_test", { | defineVirtualDevice("meta_error_test", { | ||
Строка 833: | Строка 973: | ||
} | } | ||
}); | }); | ||
</div> | |||
<div lang="ru" dir="ltr" class="mw-content-ltr"> | |||
trackMqtt("/devices/+/controls/+/meta/error", function(message){ | trackMqtt("/devices/+/controls/+/meta/error", function(message){ | ||
log.info("name: {}, value: {}".format(message.topic, message.value)) | log.info("name: {}, value: {}".format(message.topic, message.value)) | ||
Строка 844: | Строка 986: | ||
}); | }); | ||
</syntaxhighlight> | </syntaxhighlight> | ||
</div> | |||
== | <div lang="ru" dir="ltr" class="mw-content-ltr"> | ||
== Пользовательские поля в веб-интерфейсе == | |||
</div> | |||
[[File:Sample-custom-config-1.png|300px|thumb|right| | <div lang="ru" dir="ltr" class="mw-content-ltr"> | ||
[[File:Sample-custom-config-2.png|300px|thumb|right| | [[File:Sample-custom-config-1.png|300px|thumb|right|Пример конфигурации]] | ||
[[File:Sample-custom-config-2.png|300px|thumb|right|Пример скрипта]] | |||
Задача - надо в веб-интерфейсе контроллера Wiren Board вводить уставки температуры и влажности. | |||
</div> | |||
<div lang="ru" dir="ltr" class="mw-content-ltr"> | |||
Простой способ, это сделать в defineVirtualDevice() поле, ему сделать readonly: false. И оно появится в веб-интерфейсе в Devices как редактируемое, а значение будет сохраняться в движке правил. | |||
Но сложную настройку с меню и вариантами так не сделать. | |||
</div> | |||
<div lang="ru" dir="ltr" class="mw-content-ltr"> | |||
Правильный, но сложный способ — создать новую вкладку в разделе Configs с редактируемыми полями параметров установок . | |||
</div> | |||
<div lang="ru" dir="ltr" class="mw-content-ltr"> | |||
Потребуются три файла: | |||
</div> | |||
1. | <div lang="ru" dir="ltr" class="mw-content-ltr"> | ||
1. Схема вывода html странички в разделе Configs : /usr/share/wb-mqtt-confed/schemas/test-config.schema.json | |||
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
{ | { | ||
Строка 866: | Строка 1019: | ||
"title":"Test configuration", | "title":"Test configuration", | ||
"description":"Long description configuration", | "description":"Long description configuration", | ||
</div> | |||
<div lang="ru" dir="ltr" class="mw-content-ltr"> | |||
"configFile": { | "configFile": { | ||
"path":"/etc/test-config.conf", | "path":"/etc/test-config.conf", | ||
"service":"wb-rules" | "service":"wb-rules" | ||
}, | }, | ||
</div> | |||
<div lang="ru" dir="ltr" class="mw-content-ltr"> | |||
"properties": { | "properties": { | ||
"temperature_setpoint": { | "temperature_setpoint": { | ||
Строка 881: | Строка 1038: | ||
"maximum": 40 | "maximum": 40 | ||
}, | }, | ||
</div> | |||
<div lang="ru" dir="ltr" class="mw-content-ltr"> | |||
"humidity_setpoint": { | "humidity_setpoint": { | ||
"type":"number", | "type":"number", | ||
Строка 894: | Строка 1053: | ||
} | } | ||
</syntaxhighlight> | </syntaxhighlight> | ||
</div> | |||
2. | <div lang="ru" dir="ltr" class="mw-content-ltr"> | ||
2. Описание конфигурации по умолчанию (при сохранении формы в веб интерфейсе, значения запишутся в этот файл) : /etc/test-config.conf | |||
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
{ | { | ||
Строка 902: | Строка 1063: | ||
} | } | ||
</syntaxhighlight> | </syntaxhighlight> | ||
</div> | |||
3. | <div lang="ru" dir="ltr" class="mw-content-ltr"> | ||
3. Скрипт, обновляющий конфиг : /mnt/data/etc/wb-rules/test-config-script.js | |||
</div> | |||
<div lang="ru" dir="ltr" class="mw-content-ltr"> | |||
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
var config = readConfig("/etc/test-config.conf"); | var config = readConfig("/etc/test-config.conf"); | ||
</div> | |||
<div lang="ru" dir="ltr" class="mw-content-ltr"> | |||
log("temperature setpoint is: {}".format(config.temperature_setpoint)); | log("temperature setpoint is: {}".format(config.temperature_setpoint)); | ||
</syntaxhighlight> | </syntaxhighlight> | ||
</div> | |||
<div lang="ru" dir="ltr" class="mw-content-ltr"> | |||
Последний файл можно в том числе редактировать из веб-интерфейса на вкладке Scripts. | |||
</div> | |||
<div lang="ru" dir="ltr" class="mw-content-ltr"> | |||
В json файлах описаны схемы вывода html странички браузером, по общепринятому стандарту отображения. Описание ключей тут: json-schema.org. | |||
</div> | |||
<div lang="ru" dir="ltr" class="mw-content-ltr"> | |||
После создания файлов, нужно выполнить рестарт сервисов | |||
</div> | |||
<div lang="ru" dir="ltr" class="mw-content-ltr"> | |||
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
service wb-mqtt-confed restart | service wb-mqtt-confed restart | ||
</div> | |||
<div lang="ru" dir="ltr" class="mw-content-ltr"> | |||
service wb-rules restart | service wb-rules restart | ||
</syntaxhighlight> | </syntaxhighlight> | ||
</div> | |||
<div lang="ru" dir="ltr" class="mw-content-ltr"> | |||
При нажатии кнопки Save в веб-интерфейсе, будет перезапускаться сервис wb-rules, а значения установок - записываться в правила. | |||
</div> | |||
== | <div lang="ru" dir="ltr" class="mw-content-ltr"> | ||
== Сложные правила с расписаниями == | |||
</div> | |||
<div lang="ru" dir="ltr" class="mw-content-ltr"> | |||
Объект - продуктовый магазин. Различные системы магазина управляются по обратной связи от датчиков температуры и с учётом расписания работы магазина. | |||
</div> | |||
<div lang="ru" dir="ltr" class="mw-content-ltr"> | |||
Для расписаний используются не cron-правила, а обёртка над ними. Обёртка включает и выключает правила, которые, в отличие от cron-правил, выполняются постоянно, будучи включенными. | |||
</div> | |||
<div lang="ru" dir="ltr" class="mw-content-ltr"> | |||
Например, мы хотим, чтобы освещение было включено с 10 до 17ч. Обёртка (libschedule) будет выполнять правило "включить освещение" раз в минуту с 10 утра до 17 вечера. | |||
</div> | |||
<div lang="ru" dir="ltr" class="mw-content-ltr"> | |||
Это значит, что даже если контроллер работает с перерывами и пропустил время перехода между расписаниями (10 утра), то контроллер всё равно включит освещение при первой возможности. | |||
</div> | |||
<div lang="ru" dir="ltr" class="mw-content-ltr"> | |||
lib_schedules.js: | lib_schedules.js: | ||
</div> | |||
<div lang="ru" dir="ltr" class="mw-content-ltr"> | |||
<syntaxhighlight lang="ecmascript"> | <syntaxhighlight lang="ecmascript"> | ||
global.__proto__.Schedules = {}; | global.__proto__.Schedules = {}; | ||
</div> | |||
(function(Schedules) { // | <div lang="ru" dir="ltr" class="mw-content-ltr"> | ||
(function(Schedules) { // замыкание | |||
</div> | |||
function todayAt(now, hours, minutes) { | <div lang="ru" dir="ltr" class="mw-content-ltr"> | ||
function todayAt(now, hours, minutes) { | |||
var date = new Date(now); | var date = new Date(now); | ||
// i.e. "today, at HH:MM". All dates are in UTC! | // i.e. "today, at HH:MM". All dates are in UTC! | ||
Строка 949: | Строка 1146: | ||
return date; | return date; | ||
} | } | ||
</div> | |||
function checkScheduleInterval(now, start_time, end_time) { | <div lang="ru" dir="ltr" class="mw-content-ltr"> | ||
function checkScheduleInterval(now, start_time, end_time) { | |||
var start_date = todayAt(now, start_time[0], start_time[1]); | var start_date = todayAt(now, start_time[0], start_time[1]); | ||
var end_date = todayAt(now, end_time[0], end_time[1]); | var end_date = todayAt(now, end_time[0], end_time[1]); | ||
log("checkScheduleInterval {} {} {}".format(now, start_date, end_date)); | log("checkScheduleInterval {} {} {}".format(now, start_date, end_date)); | ||
</div> | |||
if (end_date >= start_date) { | <div lang="ru" dir="ltr" class="mw-content-ltr"> | ||
if (end_date >= start_date) { | |||
if ((now >= start_date) && (now < end_date)) { | if ((now >= start_date) && (now < end_date)) { | ||
return true; | return true; | ||
Строка 962: | Строка 1163: | ||
// end date is less than start date, | // end date is less than start date, | ||
// assuming they belong to a different days (e.g. today and tomorrow) | // assuming they belong to a different days (e.g. today and tomorrow) | ||
</div> | |||
// option 1: what if it's now the day of "end" date? | <div lang="ru" dir="ltr" class="mw-content-ltr"> | ||
// option 1: what if it's now the day of "end" date? | |||
// in this case the following is enough: | // in this case the following is enough: | ||
if (now < end_date) { | if (now < end_date) { | ||
return true; | return true; | ||
} | } | ||
</div> | |||
// well, that seems not to be the case. ok, | <div lang="ru" dir="ltr" class="mw-content-ltr"> | ||
// well, that seems not to be the case. ok, | |||
// option 2: it's the day of "start" date: | // option 2: it's the day of "start" date: | ||
</div> | |||
if (now >= start_date) { | <div lang="ru" dir="ltr" class="mw-content-ltr"> | ||
if (now >= start_date) { | |||
return true; | return true; | ||
} | } | ||
} | } | ||
return false; | return false; | ||
</div> | |||
} | <div lang="ru" dir="ltr" class="mw-content-ltr"> | ||
} | |||
</div> | |||
function checkSchedule(schedule, now) { | <div lang="ru" dir="ltr" class="mw-content-ltr"> | ||
function checkSchedule(schedule, now) { | |||
if (now == undefined) { | if (now == undefined) { | ||
now = new Date(); | now = new Date(); | ||
} | } | ||
</div> | |||
for (var i = 0; i < schedule.intervals.length; ++i) { | <div lang="ru" dir="ltr" class="mw-content-ltr"> | ||
for (var i = 0; i < schedule.intervals.length; ++i) { | |||
var item = schedule.intervals[i]; | var item = schedule.intervals[i]; | ||
if (checkScheduleInterval(now, item[0], item[1])) { | if (checkScheduleInterval(now, item[0], item[1])) { | ||
Строка 999: | Строка 1212: | ||
dev["_schedules"][schedule.name] = checkSchedule(schedule); | dev["_schedules"][schedule.name] = checkSchedule(schedule); | ||
}; | }; | ||
</div> | |||
function addScheduleDevCronTasks(schedule) { | <div lang="ru" dir="ltr" class="mw-content-ltr"> | ||
function addScheduleDevCronTasks(schedule) { | |||
for (var i = 0; i < schedule.intervals.length; ++i) { | for (var i = 0; i < schedule.intervals.length; ++i) { | ||
var interval = schedule.intervals[i]; | var interval = schedule.intervals[i]; | ||
Строка 1017: | Строка 1232: | ||
} | } | ||
} | } | ||
</div> | |||
function addScheduleAutoUpdCronTask(schedule) { | <div lang="ru" dir="ltr" class="mw-content-ltr"> | ||
function addScheduleAutoUpdCronTask(schedule) { | |||
defineRule("_schedule_auto_upd_{}".format(schedule.name), { | defineRule("_schedule_auto_upd_{}".format(schedule.name), { | ||
when: cron("@every " + schedule.autoUpdate), | when: cron("@every " + schedule.autoUpdate), | ||
Строка 1026: | Строка 1243: | ||
}); | }); | ||
} | } | ||
</div> | |||
var _schedules = {}; | <div lang="ru" dir="ltr" class="mw-content-ltr"> | ||
var _schedules = {}; | |||
</div> | |||
Schedules.registerSchedule = function(schedule) { | <div lang="ru" dir="ltr" class="mw-content-ltr"> | ||
Schedules.registerSchedule = function(schedule) { | |||
_schedules[schedule.name] = schedule; | _schedules[schedule.name] = schedule; | ||
}; | }; | ||
</div> | |||
Schedules.initSchedules = function() { | <div lang="ru" dir="ltr" class="mw-content-ltr"> | ||
Schedules.initSchedules = function() { | |||
var params = { | var params = { | ||
title: "Schedule Status", | title: "Schedule Status", | ||
cells: {} | cells: {} | ||
}; | }; | ||
</div> | |||
for (var schedule_name in _schedules) { | <div lang="ru" dir="ltr" class="mw-content-ltr"> | ||
for (var schedule_name in _schedules) { | |||
if (_schedules.hasOwnProperty(schedule_name)) { | if (_schedules.hasOwnProperty(schedule_name)) { | ||
var schedule = _schedules[schedule_name]; | var schedule = _schedules[schedule_name]; | ||
Строка 1045: | Строка 1270: | ||
} | } | ||
}; | }; | ||
</div> | |||
defineVirtualDevice("_schedules", params); | <div lang="ru" dir="ltr" class="mw-content-ltr"> | ||
defineVirtualDevice("_schedules", params); | |||
</div> | |||
for (var schedule_name in _schedules) { | <div lang="ru" dir="ltr" class="mw-content-ltr"> | ||
for (var schedule_name in _schedules) { | |||
if (_schedules.hasOwnProperty(schedule_name)) { | if (_schedules.hasOwnProperty(schedule_name)) { | ||
var schedule = _schedules[schedule_name]; | var schedule = _schedules[schedule_name]; | ||
</div> | |||
// setup cron tasks which updates the schedule dev status at schedule | <div lang="ru" dir="ltr" class="mw-content-ltr"> | ||
// setup cron tasks which updates the schedule dev status at schedule | |||
// interval beginings and ends | // interval beginings and ends | ||
addScheduleDevCronTasks(schedule); | addScheduleDevCronTasks(schedule); | ||
</div> | |||
// if needed, setup periodic task to trigger rules which use this schedule | <div lang="ru" dir="ltr" class="mw-content-ltr"> | ||
// if needed, setup periodic task to trigger rules which use this schedule | |||
if (schedule.autoUpdate) { | if (schedule.autoUpdate) { | ||
addScheduleAutoUpdCronTask(schedule); | addScheduleAutoUpdCronTask(schedule); | ||
} | } | ||
</div> | |||
// set schedule dev status as soon as possible at startup | <div lang="ru" dir="ltr" class="mw-content-ltr"> | ||
// set schedule dev status as soon as possible at startup | |||
(function(schedule) { | (function(schedule) { | ||
setTimeout(function() { | setTimeout(function() { | ||
Строка 1068: | Строка 1303: | ||
}, 1); | }, 1); | ||
})(schedule); | })(schedule); | ||
</div> | |||
}; | <div lang="ru" dir="ltr" class="mw-content-ltr"> | ||
}; | |||
}; | }; | ||
}; | }; | ||
</div> | |||
<div lang="ru" dir="ltr" class="mw-content-ltr"> | |||
})(Schedules); | })(Schedules); | ||
</syntaxhighlight> | </syntaxhighlight> | ||
</div> | |||
<div lang="ru" dir="ltr" class="mw-content-ltr"> | |||
Пример правил, с использованием Schedules: | |||
<syntaxhighlight lang="ecmascript"> | <syntaxhighlight lang="ecmascript"> | ||
(function() { // | (function() { // замыкание | ||
</div> | |||
defineAlias("countersTemperature", "wb-msw2_30/Temperature"); | <div lang="ru" dir="ltr" class="mw-content-ltr"> | ||
defineAlias("countersTemperature", "wb-msw2_30/Temperature"); | |||
defineAlias("vegetablesTemperature", "wb-msw2_31/Temperature"); | defineAlias("vegetablesTemperature", "wb-msw2_31/Temperature"); | ||
</div> | |||
defineAlias("heater1EnableInverted", "wb-mrm2-old_70/Relay 1"); | <div lang="ru" dir="ltr" class="mw-content-ltr"> | ||
defineAlias("heater1EnableInverted", "wb-mrm2-old_70/Relay 1"); | |||
defineAlias("frontshopVentInverted", "wb-gpio/EXT1_R3A3"); | defineAlias("frontshopVentInverted", "wb-gpio/EXT1_R3A3"); | ||
</div> | |||
Schedules.registerSchedule({ | <div lang="ru" dir="ltr" class="mw-content-ltr"> | ||
"name" : "signboard", // | Schedules.registerSchedule({ | ||
"name" : "signboard", // вывеска | |||
"autoUpdate" : "1m", | "autoUpdate" : "1m", | ||
"intervals" : [ | "intervals" : [ | ||
[ [12, 30], [20, 30] ], // | [ [12, 30], [20, 30] ], // в UTC, 15:30 - 23:30 MSK | ||
[ [3, 30], [5, 20] ], // | [ [3, 30], [5, 20] ], // в UTC, 6:30 - 8:20 MSK | ||
] | ] | ||
}); | }); | ||
Строка 1098: | Строка 1345: | ||
"autoUpdate" : "1m", | "autoUpdate" : "1m", | ||
"intervals" : [ | "intervals" : [ | ||
[ [4, 45], [20, 15] ], // | [ [4, 45], [20, 15] ], // всё ещё UTC, 7:45 - 23:15 MSK | ||
] | ] | ||
}); | }); | ||
Строка 1105: | Строка 1352: | ||
"autoUpdate" : "1m", | "autoUpdate" : "1m", | ||
"intervals" : [ | "intervals" : [ | ||
[ [5, 0], [19, 0] ], // | [ [5, 0], [19, 0] ], // всё ещё UTC, 8:00 - 22:00 MSK | ||
] | ] | ||
}); | }); | ||
Строка 1112: | Строка 1359: | ||
"autoUpdate" : "1m", | "autoUpdate" : "1m", | ||
"intervals" : [ | "intervals" : [ | ||
[ [4, 45], [19, 15] ], // | [ [4, 45], [19, 15] ], // всё ещё UTC, 7:45 - 22:15 MSK | ||
] | ] | ||
}); | }); | ||
Строка 1119: | Строка 1366: | ||
"autoUpdate" : "1m", | "autoUpdate" : "1m", | ||
"intervals" : [ | "intervals" : [ | ||
[ [4, 20], [20, 45] ], // | [ [4, 20], [20, 45] ], // всё ещё UTC, 7:20 -23:45 MSK | ||
] | ] | ||
}); | }); | ||
Строка 1125: | Строка 1372: | ||
"name" : "heaters_schedule", | "name" : "heaters_schedule", | ||
"intervals" : [ | "intervals" : [ | ||
[ [4, 0], [17, 0] ], // | [ [4, 0], [17, 0] ], // всё ещё UTC, 07:00 - 20:00 MSK дневной режим | ||
] | ] | ||
}); | }); | ||
Schedules.initSchedules(); | Schedules.initSchedules(); | ||
</div> | |||
// | <div lang="ru" dir="ltr" class="mw-content-ltr"> | ||
// Вывеска и фасадное освещение | |||
defineRule("signboardOnOff", { | defineRule("signboardOnOff", { | ||
when: function() { | when: function() { | ||
Строка 1144: | Строка 1393: | ||
} | } | ||
}); | }); | ||
</div> | |||
// | <div lang="ru" dir="ltr" class="mw-content-ltr"> | ||
// Освещение торгового зала | |||
defineRule("lightingFrontshopOnOff", { | defineRule("lightingFrontshopOnOff", { | ||
when: function() { | when: function() { | ||
Строка 1152: | Строка 1403: | ||
then: function (newValue, devName, cellName) { | then: function (newValue, devName, cellName) { | ||
log("lightingFrontshopOnOff newValue={}, devName={}, cellName={}", newValue, devName, cellName); | log("lightingFrontshopOnOff newValue={}, devName={}, cellName={}", newValue, devName, cellName); | ||
dev["wb-gpio/EXT1_R3A1"] = ! dev._schedules.frontshop_lighting; // | dev["wb-gpio/EXT1_R3A1"] = ! dev._schedules.frontshop_lighting; //инвертированный контактор | ||
} | } | ||
}); | }); | ||
</div> | |||
// | <div lang="ru" dir="ltr" class="mw-content-ltr"> | ||
// Вентиляция подсобного помещения | |||
defineRule("ventBackstoreOnOff", { | defineRule("ventBackstoreOnOff", { | ||
when: function() { | when: function() { | ||
Строка 1164: | Строка 1417: | ||
log("ventBackstoreOnOff newValue={}, devName={}, cellName={}", newValue, devName, cellName); | log("ventBackstoreOnOff newValue={}, devName={}, cellName={}", newValue, devName, cellName); | ||
var on = dev._schedules.ext_working_hours_15m; | var on = dev._schedules.ext_working_hours_15m; | ||
dev["wb-mr6c_81/K1"] = ! on; // | dev["wb-mr6c_81/K1"] = ! on; //инвертированный контактор | ||
dev["wb-mr6c_81/K5"] = ! on; // | dev["wb-mr6c_81/K5"] = ! on; //инвертированный контактор | ||
} | } | ||
}); | }); | ||
</div> | |||
// | <div lang="ru" dir="ltr" class="mw-content-ltr"> | ||
// Освещение холодильных горок | |||
defineRule("lightingCoolingshelfsOnOff", { | defineRule("lightingCoolingshelfsOnOff", { | ||
when: function() { | when: function() { | ||
Строка 1177: | Строка 1432: | ||
log("lightingCoolingshelfsOnOff newValue={}, devName={}, cellName={}", newValue, devName, cellName); | log("lightingCoolingshelfsOnOff newValue={}, devName={}, cellName={}", newValue, devName, cellName); | ||
var on = dev._schedules.working_hours_15m; | var on = dev._schedules.working_hours_15m; | ||
</div> | |||
// | <div lang="ru" dir="ltr" class="mw-content-ltr"> | ||
// освещение в горках через нормально-закрытые реле (инвертировано) | |||
dev["wb-mrm2-old_60/Relay 1"] = !on; | dev["wb-mrm2-old_60/Relay 1"] = !on; | ||
dev["wb-mrm2-old_61/Relay 1"] = !on; | dev["wb-mrm2-old_61/Relay 1"] = !on; | ||
Строка 1190: | Строка 1446: | ||
} | } | ||
}); | }); | ||
</div> | |||
// | <div lang="ru" dir="ltr" class="mw-content-ltr"> | ||
//Брендовые холодильники (пиво, лимонады) | |||
defineRule("powerBrandFridgesOnOff", { | defineRule("powerBrandFridgesOnOff", { | ||
when: function() { | when: function() { | ||
Строка 1200: | Строка 1458: | ||
var on = dev._schedules.working_hours; | var on = dev._schedules.working_hours; | ||
dev["wb-gpio/EXT1_R3A5"] = !on; // | dev["wb-gpio/EXT1_R3A5"] = !on; // инвертировано | ||
} | } | ||
}); | }); | ||
</div> | |||
// ========= | <div lang="ru" dir="ltr" class="mw-content-ltr"> | ||
// | // ========= Котлы и приточная вентиляция ТЗ =========== | ||
// обратная связь по температуре овощной зоны | |||
</div> | |||
// | <div lang="ru" dir="ltr" class="mw-content-ltr"> | ||
// днём работает позиционный регулятор | |||
defineRule("heatersDayOff", { | defineRule("heatersDayOff", { | ||
when: function() { | when: function() { | ||
Строка 1214: | Строка 1476: | ||
then: function (newValue, devName, cellName) { | then: function (newValue, devName, cellName) { | ||
log("heatersDayOff newValue={}, devName={}, cellName={}", newValue, devName, cellName); | log("heatersDayOff newValue={}, devName={}, cellName={}", newValue, devName, cellName); | ||
heater1EnableInverted = !false; // | heater1EnableInverted = !false; // инвертировано | ||
} | } | ||
}); | }); | ||
</div> | |||
defineRule("heatersDayOn", { | <div lang="ru" dir="ltr" class="mw-content-ltr"> | ||
defineRule("heatersDayOn", { | |||
when: function() { | when: function() { | ||
return (dev._schedules.heaters_schedule) && (vegetablesTemperature < 16.7); | return (dev._schedules.heaters_schedule) && (vegetablesTemperature < 16.7); | ||
Строка 1224: | Строка 1488: | ||
then: function (newValue, devName, cellName) { | then: function (newValue, devName, cellName) { | ||
log("heatersDayOn newValue={}, devName={}, cellName={}", newValue, devName, cellName); | log("heatersDayOn newValue={}, devName={}, cellName={}", newValue, devName, cellName); | ||
heater1EnableInverted = !true; // | heater1EnableInverted = !true; // инвертировано | ||
} | } | ||
}); | }); | ||
</div> | |||
// | <div lang="ru" dir="ltr" class="mw-content-ltr"> | ||
// ночью работает позиционный регулятор | |||
defineRule("heatersNightOff", { | defineRule("heatersNightOff", { | ||
when: function() { | when: function() { | ||
Строка 1235: | Строка 1501: | ||
then: function (newValue, devName, cellName) { | then: function (newValue, devName, cellName) { | ||
log("heatersNightOff newValue={}, devName={}, cellName={}", newValue, devName, cellName); | log("heatersNightOff newValue={}, devName={}, cellName={}", newValue, devName, cellName); | ||
heater1EnableInverted = !false; // | heater1EnableInverted = !false; // инвертировано | ||
} | } | ||
}); | }); | ||
</div> | |||
defineRule("heatersNightOn", { | <div lang="ru" dir="ltr" class="mw-content-ltr"> | ||
defineRule("heatersNightOn", { | |||
when: function() { | when: function() { | ||
return (!dev._schedules.heaters_schedule) && (vegetablesTemperature < 11.3); | return (!dev._schedules.heaters_schedule) && (vegetablesTemperature < 11.3); | ||
Строка 1245: | Строка 1513: | ||
then: function (newValue, devName, cellName) { | then: function (newValue, devName, cellName) { | ||
log("heatersNightOn newValue={}, devName={}, cellName={}", newValue, devName, cellName); | log("heatersNightOn newValue={}, devName={}, cellName={}", newValue, devName, cellName); | ||
heater1EnableInverted = !true; // | heater1EnableInverted = !true; // инвертировано | ||
} | } | ||
}); | }); | ||
</div> | |||
// | <div lang="ru" dir="ltr" class="mw-content-ltr"> | ||
// приточная и вытяжная вентиляция принудительно выключены | |||
defineRule("ventFrontshopAlwaysOff", { | defineRule("ventFrontshopAlwaysOff", { | ||
Строка 1259: | Строка 1529: | ||
}); | }); | ||
// ================== Кассовая зона ================= | |||
// ================== | </div> | ||
// | <div lang="ru" dir="ltr" class="mw-content-ltr"> | ||
// в кассовой зоне в рабочее время температура поддерживается кондиционерами (позиционный регулятор) | |||
</div> | |||
defineRule("countersACOn", { | <div lang="ru" dir="ltr" class="mw-content-ltr"> | ||
defineRule("countersACOn", { | |||
when: function() { | when: function() { | ||
return (dev._schedules.working_hours_15m) && (countersTemperature < 17.7); | return (dev._schedules.working_hours_15m) && (countersTemperature < 17.7); | ||
Строка 1270: | Строка 1543: | ||
then: function (newValue, devName, cellName) { | then: function (newValue, devName, cellName) { | ||
log("countersACOn newValue={}, devName={}, cellName={}", newValue, devName, cellName); | log("countersACOn newValue={}, devName={}, cellName={}", newValue, devName, cellName); | ||
dev["wb-mir_75/Play from ROM7"] = true; // | dev["wb-mir_75/Play from ROM7"] = true; // кондиционер кассовой зоны на нагрев | ||
} | } | ||
}); | }); | ||
</div> | |||
// | <div lang="ru" dir="ltr" class="mw-content-ltr"> | ||
// в нерабочее время кондиционер выключен | |||
defineRule("countersACOff", { | defineRule("countersACOff", { | ||
when: function() { | when: function() { | ||
Строка 1281: | Строка 1556: | ||
then: function (newValue, devName, cellName) { | then: function (newValue, devName, cellName) { | ||
log("countersACOff newValue={}, devName={}, cellName={}", newValue, devName, cellName); | log("countersACOff newValue={}, devName={}, cellName={}", newValue, devName, cellName); | ||
dev["wb-mir_75/Play from ROM2"] = true; // | dev["wb-mir_75/Play from ROM2"] = true; // кондиционер кассовой зоны выключить | ||
} | } | ||
}); | }); | ||
</div> | |||
// =============== | <div lang="ru" dir="ltr" class="mw-content-ltr"> | ||
// | // =============== Овощная зона ============== | ||
// Охлаждение овощей кондиционером только при температуре воздуха выше 18.5C | |||
</div> | |||
defineRule("acVegOn", { | <div lang="ru" dir="ltr" class="mw-content-ltr"> | ||
defineRule("acVegOn", { | |||
when: function() { | when: function() { | ||
return vegetablesTemperature >= 18.5 | return vegetablesTemperature >= 18.5 | ||
Строка 1294: | Строка 1573: | ||
then: function (newValue, devName, cellName) { | then: function (newValue, devName, cellName) { | ||
log("acVegOn newValue={}, devName={}, cellName={}", newValue, devName, cellName); | log("acVegOn newValue={}, devName={}, cellName={}", newValue, devName, cellName); | ||
dev["wb-mir_76/Play from ROM3"] = true; // | dev["wb-mir_76/Play from ROM3"] = true; // Охлаждение +18 | ||
} | } | ||
}); | }); | ||
</div> | |||
defineRule("acVegOff", { | <div lang="ru" dir="ltr" class="mw-content-ltr"> | ||
defineRule("acVegOff", { | |||
when: function() { | when: function() { | ||
return vegetablesTemperature < 17.8 | return vegetablesTemperature < 17.8 | ||
Строка 1304: | Строка 1585: | ||
then: function (newValue, devName, cellName) { | then: function (newValue, devName, cellName) { | ||
log("acVegOff newValue={}, devName={}, cellName={}", newValue, devName, cellName); | log("acVegOff newValue={}, devName={}, cellName={}", newValue, devName, cellName); | ||
dev["wb-mir_76/Play from ROM2"] = true; // | dev["wb-mir_76/Play from ROM2"] = true; // выключить | ||
} | } | ||
}); | }); | ||
Строка 1310: | Строка 1591: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
== Полезные ссылки == | == Полезные ссылки == | ||
* [[Wb-rules | | * [[Wb-rules | Краткое описание wb-rules на wiki]] | ||
* [https://github.com/wirenboard/wb-rules | * [https://github.com/wirenboard/wb-rules Полное описание wb-rules на Github] | ||
</div> |
Версия 20:48, 21 сентября 2022
Control tracking
This simplest rule keeps track of a control and sets another control to the same state.
For example, a rule can turn on a siren and a lamp if a motion sensor detects movement.
In the example, the motion sensor is connected to the "dry contact" input, control type "switch". The siren is connected to the built-in Wiren Board relay, and the lamp is connected to the relay box via Modbus. When the "dry contact" input (motion sensor output) is closed, "1" is supplied to the lamp and the relay, and "0" when it is off.
The rule is triggered every time the control value "D1_IN" of the device "wb-gpio" is changed. The new value of this control is passed to the rule code as a variable newValue.
defineRule({
whenChanged: "wb-gpio/D1_IN",
then: function (newValue, devName, cellName) {
dev["wb-gpio"]["Relay_2"] = newValue;
dev["wb-mrm2_6"]["Relay 1"] = newValue;
}
});
The same, but with a virtual device as an event source. Usage example: scripted button that turns on/off the siren and light bulb.
defineVirtualDevice("simple_test", {
title: "Simple switch",
cells: {
enabled: {
type: "switch",
value: false
},
}
});
defineRule("simple_switch", {
whenChanged: "simple_test/enabled",
then: function (newValue, devName, cellName) {
dev["wb-gpio"]["Relay_2"] = newValue;
dev["wb-mrm2_6"]["Relay 1"] = newValue;
}
});
Motion detection with timeout
A motion detector with a "dry contact" output is connected to input D2. When motion is detected, it shorts D2 and GND and status "1" appears on the corresponding wb-gpio/D2_IN
channel.
The lighting is connected via a built-in relay corresponding to the wb-gpio/Relay_1
channel.
The rule works like this:
- when movement appears, the light turns on. If a thirty-second "off" timer was previously started, this timer is disabled;
- when motion is lost, a thirty second "off" timer is started. If he manages to reach the end, the light turns off.
var motion_timer_1_timeout_ms = 30 * 1000;
var motion_timer_1_id = null;
defineRule("motion_detector_1", {
whenChanged: "wb-gpio/D2_IN",
then: function (newValue, devName, cellName) {
if (newValue) {
dev["wb-gpio"]["Relay_1"] = true;
if (motion_timer_1_id) {
clearTimeout(motion_timer_1_id);
}
motion_timer_1_id = setTimeout(function () {
dev["wb-gpio"]["Relay_1"] = false;
motion_timer_1_id = null;
}, motion_timer_1_timeout_ms);
}
},
});
Creating similar rules
If you need several such motion detectors, then in order not to copy the code, you can wrap the creation of the rule and variables in a function:
function makeMotionDetector(name, timeout_ms, detector_control, relay_control) {
var motion_timer_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);
}
motion_timer_id = setTimeout(function() {
dev["wb-gpio"][relay_control] = false;
motion_timer_id = null;
}, timeout_ms);
}
}
});
}
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");
Activate the rule only at a certain time
The rule is the same as in the previous section, but only runs from 9:30 am to 5:10 pm UTC.
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();
// 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);
// time point marking the end of the interval
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);
}
}
}
});
Rolling shutters
One relay turns on the motor that raises the curtains, the second relay turns on the motor that lowers the curtains. The rule ensures that both relays are not turned on at the same time.
In addition, the rule turns off the engines after a specified time after being turned on.
(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";
var relay_down_device = "lc103_4";
var relay_down_control = "Relay 2";
var timeout_s = 15;
// End of settings
var relay_up_timer_id = null;
var relay_down_timer_id = null;
defineRule( "roller_shutter_up_on" + suffix, {
asSoonAs: function() {
return dev[relay_up_device][relay_up_control];
},
then: function () {
if (relay_up_timer_id) {
relay_up_timer_id = clearTimeout(relay_up_timer_id);
};
relay_up_timer_id = setTimeout(function() {
return dev[relay_up_device][relay_up_control] = 0;
}, timeout_s * 1000);
}
});
defineRule("roller_shutter_down_on" + suffix, {
asSoonAs: function() {
return dev[relay_down_device][relay_down_control];
},
then: function () {
if (relay_down_timer_id) {
relay_down_timer_id = clearTimeout(relay_down_timer_id);
};
relay_down_timer_id = setTimeout(function() {
dev[relay_down_device][relay_down_control] = 0;
}, timeout_s * 1000);
}
});
defineRule("roller_shutter_both_on" + suffix, {
asSoonAs: function() {
return dev[relay_up_device][relay_up_control] && dev[relay_down_device][relay_down_control];
},
then: function () {
if (relay_up_timer_id) {
relay_up_timer_id = clearTimeout(relay_up_timer_id);
};
if (relay_down_timer_id) {
relay_down_timer_id = clearTimeout(relay_down_timer_id);
};
dev[relay_up_device][relay_up_control] = 0;
dev[relay_down_device][relay_down_control] = 0;
log("Both roller shutter relays on, switching them off");
}
});
})();
An older version of the same script demonstrates the use of aliases:
(function() {
defineAlias("relay_up_1", "lc103_4/Relay 1");
defineAlias("relay_down_1", "lc103_4/Relay 2");
var timeout_s = 15;
defineRule("roller_shutter_1_up_on", {
asSoonAs: function() {
return relay_up_1;
},
then: function () {
setTimeout(function() {
relay_up_1 = 0;
}, timeout_s * 1000);
}
});
defineRule("roller_shutter_1_down_on", {
asSoonAs: function() {
return relay_down_1;
},
then: function () {
setTimeout(function() {
relay_down_1 = 0;
}, timeout_s * 1000);
}
});
defineRule("roller_shutter_1_both_on", {
asSoonAs: function() {
return relay_up_1 && relay_down_1;
},
then: function () {
relay_up_1 = 0;
relay_down_1 = 0;
log("Both roller shutter relays on, switching them off");
}
});
})();
Impulse counters
Pulse counter connected to WB-MCM8. Gives out 1 pulse per 10 liters of water. When connected, the meter showed readings of 123.120 m³, which is equal to 123120 liters of water. The WB-MCM8 had 7 pulses when plugged in.
var meterCorrection = 123120 // Кcorrection value of the meter in liters
var counterCorrection = 7 // WB-MCM8 correction value in pulses
var inpulseValue = 10 // Number of liters per impulse
defineVirtualDevice("water_meters", { //
We create a virtual device for display in the web interface.
title: "Water meters",
cells: {
water_meter_1: {
type: "value",
value: 0
},
}
});
defineRule("water_meter_1", {
whenChanged: "wb-mcm8_29/Input 1 counter",
then: function(newValue, devName, cellName) {
if(newValue){
dev["water_meters"]["water_meter_1"] = ((parseInt(newValue) - counterCorrection) * inpulseValue) + meterCorrection; // We multiply the value of the counter by the number of liters / pulse and add the correction value.
}
}
});
Handling click counters
Description
The latest firmware versions of Wiren Board devices can recognize the types of button presses connected to the inputs and broadcast them via Modbus to the Wiren Board controller. For information on how the device recognizes types of clicks, read its documentation.
To process clicks, you need to track the state of the counter of the desired type of click on the controller and, when it changes, perform an action.
Handling counters is conveniently done on wb-rules, but you can use any automation tool like Node-RED. To speed up meter polling, configure poll period.
Examples
In the example, we are using the WB-MCM8 to control the first dimmer channel WB-MDM3:
- Short press turns on the channel.
- Double - turns off the channel.
- Long - increases brightness.
- Short, then long - reduces brightness.
Since changing the brightness requires a time-consuming action, we use a timer. We also control the state of the input with the button and stop the action when the button is released.
/* ---------------------------- */
/* 1. Single Press Counter: On action*/
/* ---------------------------- */
defineRule({
whenChanged: "wb-mcm8_20/Input 1 Single Press Counter",
then: function (newValue, devName, cellName) {
dev["wb-mdm3_58/K1"] = true;
}
});
/* ---------------------------- */
/* 2. Double Press Counter: Off action*/
/* ---------------------------- */
defineRule({
whenChanged: "wb-mcm8_20/Input 1 Double Press Counter",
then: function (newValue, devName, cellName) {
dev["wb-mdm3_58/K1"] = false;
}
});
/* --------------------------------------- */
/* 3. Long Press Counter: Increase brightness */
/* --------------------------------------- */
defineRule({
whenChanged: "wb-mcm8_20/Input 1 Long Press Counter",
then: function (newValue, devName, cellName) {
// Start a timer that will increase the value of the control
startTicker("input1_long_press", 75);
}
});
// A rule that will increase the brightness on a timer
defineRule({
when: function () { return timers["input1_long_press"].firing; },
then: function () {
var i = dev["wb-mdm3_58/Channel 1"];
if (i < 100 && dev["wb-mcm8_20/Input 1"]) {
i++
dev["wb-mdm3_58/Channel 1"] = i
} else {
timers["input1_long_press"].stop();
}
}
});
/* -------------------------------------------- */
/* 4. Shortlong Press Counter: Decrease brightness */
/* -------------------------------------------- */
defineRule({
whenChanged: "wb-mcm8_20/Input 1 Shortlong Press Counter",
then: function (newValue, devName, cellName) {
// Start a timer that will decrease the value of the control
startTicker("input1_shortlong_press", 75);
}
});
// A rule that will decrease the brightness on a timer
defineRule({
when: function () { return timers["input1_shortlong_press"].firing; },
then: function () {
var i = dev["wb-mdm3_58/Channel 1"];
if (i > 0 && dev["wb-mcm8_20/Input 1"]) {
i--
dev["wb-mdm3_58/Channel 1"] = i
} else {
timers["input1_shortlong_press"].stop();
}
}
});
Generic module for wb-rules
We wrote a module for wb-rules wb-press-actions that makes it easy to handle clicks in your scripts.
Sensor MSW v.3
When connecting the WB-MSW v.3 sensor to the Wiren Board controller, it is possible to create interesting scenarios using data from the sensor. For example, turn on the light when moving, signal with LEDs when the CO2 or VOC value is exceeded, turn on the air conditioner if it is hot or the air humidifier if the air is too dry. Rules are created individually for tasks. Here we will give some examples to understand the principle of working with the sensor. More examples of writing rules can be found in the Wb-rules rule engine documentation.
CO2
When the CO2 concentration is less than 650, we flash green once every 10 seconds.
When the CO2 concentration is over 651, but less than 1000, we flash yellow once every 5 seconds.
When the CO2 concentration is over 1001, we flash red once a second.
Max Motion
"Max Motion" - the maximum value of the motion sensor for N time. Time from 1 to 60 seconds can be set in register 282. The default is 10 seconds. When the Max Motion value reaches 50, we check whether the room is sufficiently lit, if not, turn on the light. As soon as the Max Motion value drops below 50, turn off the light.
defineRule("msw3_Motion", {
whenChanged: "wb-msw-v3_97/Max Motion",
then: function(newValue, devName, cellName) {
if (newValue > 50) {
if (dev["wb-msw-v3_97"]["Illuminance"] < 50) {
dev["wb-mr3_11"]["K1"] = true;
}
} else {
dev["wb-mr3_11"]["K1"] = false;
}
}
});
System rules
Many of the indications that are visible in the web interface of the controller out of the box are also created by rules on the wb-rules engine. Their code is here: https://github.com/wirenboard/wb-rules-system. The system rules are collected in the wb-rules-system
package, the script files on the controller are located in the /usr/share/wb-rules-system/
folder.
Несколько примеров системных правил ниже.
Правило для пищалки
Правило создаёт виртуальное устройство buzzer с ползунками для регулировки громкости и частоты, а также кнопкой включения звука.
defineVirtualDevice("buzzer", {
title: "Buzzer", //
</div>
<div lang="ru" dir="ltr" class="mw-content-ltr">
cells: {
frequency : {
type : "range",
value : 3000,
max : 7000,
},
volume : {
type : "range",
value : 10,
max : 100,
},
enabled : {
type : "switch",
value : false,
},
}
});
</div>
<div lang="ru" dir="ltr" class="mw-content-ltr">
// setup pwm2
runShellCommand("echo 2 > /sys/class/pwm/pwmchip0/export");
</div>
<div lang="ru" dir="ltr" class="mw-content-ltr">
function _buzzer_set_params() {
var period = parseInt(1.0 / dev.buzzer.frequency * 1E9);
var duty_cycle = parseInt(dev.buzzer.volume * 1.0 / 100 * period * 0.5);
</div>
<div lang="ru" dir="ltr" class="mw-content-ltr">
runShellCommand("echo " + period + " > /sys/class/pwm/pwmchip0/pwm2/period");
runShellCommand("echo " + duty_cycle + " > /sys/class/pwm/pwmchip0/pwm2/duty_cycle");
};
</div>
<div lang="ru" dir="ltr" class="mw-content-ltr">
defineRule("_system_buzzer_params", {
whenChanged: [
"buzzer/frequency",
"buzzer/volume",
],
</div>
<div lang="ru" dir="ltr" class="mw-content-ltr">
then: function (newValue, devName, cellName) {
if ( dev.buzzer.enabled) {
_buzzer_set_params();
}
}
});
</div>
<div lang="ru" dir="ltr" class="mw-content-ltr">
defineRule("_system_buzzer_onof", {
whenChanged: "buzzer/enabled",
then: function (newValue, devName, cellName) {
if ( dev.buzzer.enabled) {
_buzzer_set_params();
runShellCommand("echo 1 > /sys/class/pwm/pwmchip0/pwm2/enable");
} else {
runShellCommand("echo 0 > /sys/class/pwm/pwmchip0/pwm2/enable");
}
}
});
</div>
<div lang="ru" dir="ltr" class="mw-content-ltr">
Правило для статуса питания
Правило создаёт виртуальное устройство, которое сообщает текущий статус питания. В качестве входных данных используется два канала АЦП: измерение напряжения на аккумуляторе и измерение входного напряжения.
Реализована следующая логика:
1. Если входное напряжение меньше напряжение на аккумуляторе, то значит плата питается от аккумулятора. В этом случае, также отображается 0V в качестве входного напряжения.
2. Если входное напряжение больше напряжения на аккумуляторе, то плата работает от внешнего источника питания. В качестве входонго напряжения отображается измерение с канала Vin.
Для иллюстрации правила используют два разных способа срабатывания: по изменению значения контрола (правило _system_track_vin) и по изменению значения выражения (два других).
</div>
<div lang="ru" dir="ltr" class="mw-content-ltr">
defineVirtualDevice("power_status", {
title: "Power status", //
</div>
<div lang="ru" dir="ltr" class="mw-content-ltr">
cells: {
'working on battery' : {
type : "switch",
value : false,
readonly : true
},
'Vin' : {
type : "voltage",
value : 0
}
</div>
<div lang="ru" dir="ltr" class="mw-content-ltr">
}
});
</div>
<div lang="ru" dir="ltr" class="mw-content-ltr">
defineRule("_system_track_vin", {
whenChanged: "wb-adc/Vin",
then: function() {
if (dev["wb-adc"]["Vin"] < dev["wb-adc"]["BAT"] ) {
dev["power_status"]["Vin"] = 0;
} else {
dev["power_status"]["Vin"] = dev["wb-adc"]["Vin"] ;
}
}
});
</div>
<div lang="ru" dir="ltr" class="mw-content-ltr">
defineRule("_system_dc_on", {
asSoonAs: function () {
return dev["wb-adc"]["Vin"] > dev["wb-adc"]["BAT"];
},
then: function () {
dev["power_status"]["working on battery"] = false;
}
});
</div>
<div lang="ru" dir="ltr" class="mw-content-ltr">
defineRule("_system_dc_off", {
asSoonAs: function () {
return dev["wb-adc"]["Vin"] <= dev["wb-adc"]["BAT"];
},
then: function () {
dev["power_status"]["working on battery"] = true;
}
});
</div>
<div lang="ru" dir="ltr" class="mw-content-ltr">
Термостат
Пример простого термостата из темы на портале поддержки.
defineVirtualDevice("Termostat", {
title: "Termostat",
cells: {
// =============== Прихожая теплый пол
"R01-TS16-1-mode": {//режим 0-ручной 1-по расписанию
type: "switch",
value: false,
},
"R01-TS16-1-setpoint": {//уставка
type: "range",
value: 25,
max: 30,
readonly: false
},
"R01-TS16-1-lock": {//блокировка в визуализации 0-снята 1-заблокирована
type: "switch",
value: false,
},.......
</div>
<div lang="ru" dir="ltr" class="mw-content-ltr">
var hysteresis = 0.5;
function Termostat(name, temp, setpoint, TS, TS_onoff) {
defineRule(name, {
whenChanged: temp, //при изменении состояния датчика
then: function (newValue, devName, cellName) { //выполняй следующие действия
if (dev[TS_onoff]) {
if ( newValue < dev[setpoint] - hysteresis) { //если температура датчика меньше уставки - гистерезис
dev[TS] = true;
}
if ( newValue > dev[setpoint] + hysteresis) { //если температура датчика больше виртуальной уставки + гистерезис
dev[TS] = false;
}
}
else dev[TS] = false;
}
});
}
</div>
<div lang="ru" dir="ltr" class="mw-content-ltr">
Termostat("R01-TS16-1", "A60-M1W3/External Sensor 1", "Termostat/R01-TS16-1-setpoint", "wb-gpio/EXT4_R3A1", "Termostat/R01-TS16-1-onoff"); // Прихожая теплый пол
</div>
<div lang="ru" dir="ltr" class="mw-content-ltr">
Отправка команд по RS-485
Для примера отправим команду устройству на порт /dev/ttyNSC0 (соответствует аппаратному порту RS-485-ISO на Wiren Board 4). Для этого будем использовать движок правил и возможность выполнения произвольных shell-команд. Подробнее см. в документации.
С помощью движка правил создадим виртуальное устройство с контролом типа switch (переключатель).
При включении переключателя будем отправлять команду (уст. Яркость кан. 00=0xff) для Uniel UCH-M141:
FF FF 0A 01 FF 00 00 0A
При выключении переключателя будем отправлять команду (уст. Яркость кан. 00=0x00) для Uniel UCH-M141:
FF FF 0A 01 00 00 00 0B
1. Настройка порта
Для настройки порта /dev/ttyNSC0 на скорость 9600 надо выполнить следующую команду
stty -F /dev/ttyNSC0 ospeed 9600 ispeed 9600 raw clocal -crtscts -parenb -echo cs8
2. Отправка команды
Отправка данных делается следующей шелл-командой:
/usr/bin/printf '\xFF\xFF\x0A\x01\xD1\x06\x00\xE2' >/dev/ttyNSC0
где "\xFF\xFF\x0A\x01\xD1\x06\x00\xE2" - это запись команды "FF FF 0A 01 D1 06 00 E2".
3. Создадим в движке правил новый файл с правилами /etc/wb-rules/rs485_cmd.js
Файл можно редактировать с помощью vim, nano или mcedit в сеансе ssh на устройстве, либо залить его с помощью SCP.
root@wirenboard:~# mcedit /etc/wb-rules/rs485_cmd.js
4. Описываем в файле виртуальный девайс
defineVirtualDevice("rs485_cmd", {
title: "Send custom command to RS-485 port",
cells: {
enabled: {
type: "switch",
value: false
},
}
});
5. Перезапускаем wb-rules и проверяем работу
root@wirenboard:~# /etc/init.d/wb-rules restart root@wirenboard:~# tail -f /var/log/messages
В логе не должно быть сообщений об ошибке (выход через control-c)
В веб-интерфейсе в разделе Devices должно появиться новое устройство "Send custom command to RS-485 port".
6. Добавим функцию для конфигурирования порта.
function setup_port() {
runShellCommand("stty -F /dev/ttyNSC0 ospeed 9600 ispeed 9600 raw clocal -crtscts -parenb -echo cs8");
}
</div>
<div lang="ru" dir="ltr" class="mw-content-ltr">
7. Опишем правила на включение и выключение переключателя
defineRule("_rs485_switch_on", {
asSoonAs: function () {
return dev.rs485_cmd.enabled;
},
then: function() {
runShellCommand("/usr/bin/printf '\\xff\\xff\\x0a\\x01\\xff\\x00\\x00\\x0a' > /dev/ttyNSC0");
}
});
</div>
<div lang="ru" dir="ltr" class="mw-content-ltr">
defineRule("_rs485_switch_off", {
asSoonAs: function () {
return !dev.rs485_cmd.enabled;
},
then: function() {
runShellCommand("/usr/bin/printf '\\xff\\xff\\x0a\\x01\\x00\\x00\\x00\\x0b' >/dev/ttyNSC0");
}
});
</div>
<div lang="ru" dir="ltr" class="mw-content-ltr">
Обратите внимание на двойное экранирование.
7. Собираем всё вместе
Полное содержимое файла с правилами:
defineVirtualDevice("rs485_cmd", {
title: "Send custom command to RS-485 port",
cells: {
enabled: {
type: "switch",
value: false
},
}
});
</div>
<div lang="ru" dir="ltr" class="mw-content-ltr">
function setup_port() {
runShellCommand("stty -F /dev/ttyNSC0 ospeed 9600 ispeed 9600 raw clocal -crtscts -parenb -echo cs8");
}
</div>
<div lang="ru" dir="ltr" class="mw-content-ltr">
defineRule("_rs485_switch_on", {
asSoonAs: function () {
return dev.rs485_cmd.enabled;
},
then: function() {
runShellCommand("/usr/bin/printf '\\xff\\xff\\x0a\\x01\\xff\\x00\\x00\\x0a' > /dev/ttyNSC0");
}
});
</div>
<div lang="ru" dir="ltr" class="mw-content-ltr">
defineRule("_rs485_switch_off", {
asSoonAs: function () {
return !dev.rs485_cmd.enabled;
},
then: function() {
runShellCommand("/usr/bin/printf '\\xff\\xff\\x0a\\x01\\x00\\x00\\x00\\x0b' >/dev/ttyNSC0");
}
});
</div>
<div lang="ru" dir="ltr" class="mw-content-ltr">
setTimeout(setup_port, 1000); // запланировать выполнение setup_port() через 1 секунду после старта правил.
</div>
<div lang="ru" dir="ltr" class="mw-content-ltr">
Отправка сообщения через Telegram-бота
Сообщения отправляются с использованием Telegram API через curl
.
var message = "Text"; // напишите свой текст сообщения
var token = "TOKEN"; // замените на токен бота
var chat_id = CHATID; // замените на свой chat_id
var command = 'curl -s -X POST https://api.telegram.org/bot{}/sendMessage -d chat_id={} -d text="{}"'.format(token, chat_id, message);
</div>
<div lang="ru" dir="ltr" class="mw-content-ltr">
runShellCommand(command);
Обработка ошибок в работе с serial-устройствами
Реализована через подписку на все топики meta/error.
defineVirtualDevice("meta_error_test", {
title: "Metaerordisplay",
cells: {
topic: {
type: "text",
value: "",
readonly: true
},
value: {
type: "text",
value: "",
readonly: true
},
}
});
</div>
<div lang="ru" dir="ltr" class="mw-content-ltr">
trackMqtt("/devices/+/controls/+/meta/error", function(message){
log.info("name: {}, value: {}".format(message.topic, message.value))
if (message.value=="r"){
dev["meta_error_test/topic"] = message.topic;
dev["meta_error_test/value"] = message.value;
}
});
Пользовательские поля в веб-интерфейсе
Простой способ, это сделать в defineVirtualDevice() поле, ему сделать readonly: false. И оно появится в веб-интерфейсе в Devices как редактируемое, а значение будет сохраняться в движке правил. Но сложную настройку с меню и вариантами так не сделать.
Правильный, но сложный способ — создать новую вкладку в разделе Configs с редактируемыми полями параметров установок .
Потребуются три файла:
1. Схема вывода html странички в разделе Configs : /usr/share/wb-mqtt-confed/schemas/test-config.schema.json
{
"type":"object",
"title":"Test configuration",
"description":"Long description configuration",
</div>
<div lang="ru" dir="ltr" class="mw-content-ltr">
"configFile": {
"path":"/etc/test-config.conf",
"service":"wb-rules"
},
</div>
<div lang="ru" dir="ltr" class="mw-content-ltr">
"properties": {
"temperature_setpoint": {
"type":"number",
"title":"Temperature Setpoint (Degrees C)",
"default": 25,
"propertyOrder": 1,
"minimum": 5,
"maximum": 40
},
</div>
<div lang="ru" dir="ltr" class="mw-content-ltr">
"humidity_setpoint": {
"type":"number",
"title":"Humidity Setpoint (RH, %)",
"default": 60,
"propertyOrder": 2,
"minimum": 10,
"maximum": 95
}
},
"required": ["temperature_setpoint", "humidity_setpoint"]
}
2. Описание конфигурации по умолчанию (при сохранении формы в веб интерфейсе, значения запишутся в этот файл) : /etc/test-config.conf
{
"temperature_setpoint": 60,
"humidity_setpoint": 14
}
3. Скрипт, обновляющий конфиг : /mnt/data/etc/wb-rules/test-config-script.js
var config = readConfig("/etc/test-config.conf");
</div>
<div lang="ru" dir="ltr" class="mw-content-ltr">
log("temperature setpoint is: {}".format(config.temperature_setpoint));
Последний файл можно в том числе редактировать из веб-интерфейса на вкладке Scripts.
В json файлах описаны схемы вывода html странички браузером, по общепринятому стандарту отображения. Описание ключей тут: json-schema.org.
После создания файлов, нужно выполнить рестарт сервисов
service wb-mqtt-confed restart
</div>
<div lang="ru" dir="ltr" class="mw-content-ltr">
service wb-rules restart
При нажатии кнопки Save в веб-интерфейсе, будет перезапускаться сервис wb-rules, а значения установок - записываться в правила.
Сложные правила с расписаниями
Объект - продуктовый магазин. Различные системы магазина управляются по обратной связи от датчиков температуры и с учётом расписания работы магазина.
Для расписаний используются не cron-правила, а обёртка над ними. Обёртка включает и выключает правила, которые, в отличие от cron-правил, выполняются постоянно, будучи включенными.
Например, мы хотим, чтобы освещение было включено с 10 до 17ч. Обёртка (libschedule) будет выполнять правило "включить освещение" раз в минуту с 10 утра до 17 вечера.
Это значит, что даже если контроллер работает с перерывами и пропустил время перехода между расписаниями (10 утра), то контроллер всё равно включит освещение при первой возможности.
lib_schedules.js:
global.__proto__.Schedules = {};
</div>
<div lang="ru" dir="ltr" class="mw-content-ltr">
(function(Schedules) { // замыкание
</div>
<div lang="ru" dir="ltr" class="mw-content-ltr">
function todayAt(now, hours, minutes) {
var date = new Date(now);
// i.e. "today, at HH:MM". All dates are in UTC!
date.setHours(hours);
date.setMinutes(minutes);
return date;
}
</div>
<div lang="ru" dir="ltr" class="mw-content-ltr">
function checkScheduleInterval(now, start_time, end_time) {
var start_date = todayAt(now, start_time[0], start_time[1]);
var end_date = todayAt(now, end_time[0], end_time[1]);
log("checkScheduleInterval {} {} {}".format(now, start_date, end_date));
</div>
<div lang="ru" dir="ltr" class="mw-content-ltr">
if (end_date >= start_date) {
if ((now >= start_date) && (now < end_date)) {
return true;
}
} else {
// end date is less than start date,
// assuming they belong to a different days (e.g. today and tomorrow)
</div>
<div lang="ru" dir="ltr" class="mw-content-ltr">
// option 1: what if it's now the day of "end" date?
// in this case the following is enough:
if (now < end_date) {
return true;
}
</div>
<div lang="ru" dir="ltr" class="mw-content-ltr">
// well, that seems not to be the case. ok,
// option 2: it's the day of "start" date:
</div>
<div lang="ru" dir="ltr" class="mw-content-ltr">
if (now >= start_date) {
return true;
}
}
return false;
</div>
<div lang="ru" dir="ltr" class="mw-content-ltr">
}
</div>
<div lang="ru" dir="ltr" class="mw-content-ltr">
function checkSchedule(schedule, now) {
if (now == undefined) {
now = new Date();
}
</div>
<div lang="ru" dir="ltr" class="mw-content-ltr">
for (var i = 0; i < schedule.intervals.length; ++i) {
var item = schedule.intervals[i];
if (checkScheduleInterval(now, item[0], item[1])) {
log("found matching schedule interval at {}".format(item));
return true;
}
}
return false;
}
function updateSingleScheduleDevStatus(schedule) {
log("updateSingleScheduleDevStatus {}".format(schedule.name));
dev["_schedules"][schedule.name] = checkSchedule(schedule);
};
</div>
<div lang="ru" dir="ltr" class="mw-content-ltr">
function addScheduleDevCronTasks(schedule) {
for (var i = 0; i < schedule.intervals.length; ++i) {
var interval = schedule.intervals[i];
for (var j = 0; j < 2; ++j) { // either start or end of the interval
var hours = interval[j][0];
var minutes = interval[j][1];
log("cron at " + "0 " + minutes + " " + hours + " * * *");
defineRule("_schedule_dev_{}_{}_{}".format(schedule.name, i, j), {
when: cron("0 " + minutes + " " + hours + " * * *"),
then: function () {
log("_schedule_dev_ {}_{}_{}".format(schedule.name, i, j));
updateSingleScheduleDevStatus(schedule);
}
});
}
}
}
</div>
<div lang="ru" dir="ltr" class="mw-content-ltr">
function addScheduleAutoUpdCronTask(schedule) {
defineRule("_schedule_auto_upd_{}".format(schedule.name), {
when: cron("@every " + schedule.autoUpdate),
then: function() {
dev._schedules[schedule.name] = dev._schedules[schedule.name];
}
});
}
</div>
<div lang="ru" dir="ltr" class="mw-content-ltr">
var _schedules = {};
</div>
<div lang="ru" dir="ltr" class="mw-content-ltr">
Schedules.registerSchedule = function(schedule) {
_schedules[schedule.name] = schedule;
};
</div>
<div lang="ru" dir="ltr" class="mw-content-ltr">
Schedules.initSchedules = function() {
var params = {
title: "Schedule Status",
cells: {}
};
</div>
<div lang="ru" dir="ltr" class="mw-content-ltr">
for (var schedule_name in _schedules) {
if (_schedules.hasOwnProperty(schedule_name)) {
var schedule = _schedules[schedule_name];
params.cells[schedule_name] = {type: "switch", value: false, readonly: true};
}
};
</div>
<div lang="ru" dir="ltr" class="mw-content-ltr">
defineVirtualDevice("_schedules", params);
</div>
<div lang="ru" dir="ltr" class="mw-content-ltr">
for (var schedule_name in _schedules) {
if (_schedules.hasOwnProperty(schedule_name)) {
var schedule = _schedules[schedule_name];
</div>
<div lang="ru" dir="ltr" class="mw-content-ltr">
// setup cron tasks which updates the schedule dev status at schedule
// interval beginings and ends
addScheduleDevCronTasks(schedule);
</div>
<div lang="ru" dir="ltr" class="mw-content-ltr">
// if needed, setup periodic task to trigger rules which use this schedule
if (schedule.autoUpdate) {
addScheduleAutoUpdCronTask(schedule);
}
</div>
<div lang="ru" dir="ltr" class="mw-content-ltr">
// set schedule dev status as soon as possible at startup
(function(schedule) {
setTimeout(function() {
updateSingleScheduleDevStatus(schedule);
}, 1);
})(schedule);
</div>
<div lang="ru" dir="ltr" class="mw-content-ltr">
};
};
};
</div>
<div lang="ru" dir="ltr" class="mw-content-ltr">
})(Schedules);
Пример правил, с использованием Schedules:
(function() { // замыкание
</div>
<div lang="ru" dir="ltr" class="mw-content-ltr">
defineAlias("countersTemperature", "wb-msw2_30/Temperature");
defineAlias("vegetablesTemperature", "wb-msw2_31/Temperature");
</div>
<div lang="ru" dir="ltr" class="mw-content-ltr">
defineAlias("heater1EnableInverted", "wb-mrm2-old_70/Relay 1");
defineAlias("frontshopVentInverted", "wb-gpio/EXT1_R3A3");
</div>
<div lang="ru" dir="ltr" class="mw-content-ltr">
Schedules.registerSchedule({
"name" : "signboard", // вывеска
"autoUpdate" : "1m",
"intervals" : [
[ [12, 30], [20, 30] ], // в UTC, 15:30 - 23:30 MSK
[ [3, 30], [5, 20] ], // в UTC, 6:30 - 8:20 MSK
]
});
Schedules.registerSchedule({
"name" : "ext_working_hours_15m",
"autoUpdate" : "1m",
"intervals" : [
[ [4, 45], [20, 15] ], // всё ещё UTC, 7:45 - 23:15 MSK
]
});
Schedules.registerSchedule({
"name" : "working_hours",
"autoUpdate" : "1m",
"intervals" : [
[ [5, 0], [19, 0] ], // всё ещё UTC, 8:00 - 22:00 MSK
]
});
Schedules.registerSchedule({
"name" : "working_hours_15m",
"autoUpdate" : "1m",
"intervals" : [
[ [4, 45], [19, 15] ], // всё ещё UTC, 7:45 - 22:15 MSK
]
});
Schedules.registerSchedule({
"name" : "frontshop_lighting",
"autoUpdate" : "1m",
"intervals" : [
[ [4, 20], [20, 45] ], // всё ещё UTC, 7:20 -23:45 MSK
]
});
Schedules.registerSchedule({
"name" : "heaters_schedule",
"intervals" : [
[ [4, 0], [17, 0] ], // всё ещё UTC, 07:00 - 20:00 MSK дневной режим
]
});
Schedules.initSchedules();
</div>
<div lang="ru" dir="ltr" class="mw-content-ltr">
// Вывеска и фасадное освещение
defineRule("signboardOnOff", {
when: function() {
return dev._schedules.signboard || true;
},
then: function (newValue, devName, cellName) {
log("signboardOnOff newValue={}, devName={}, cellName={}", newValue, devName, cellName);
var on = dev._schedules.signboard; //
dev["wb-mr6c_80/K2"] = !on;
dev["wb-mr6c_80/K1"] = !on;
dev["wb-mr6c_80/K3"] = !on;
}
});
</div>
<div lang="ru" dir="ltr" class="mw-content-ltr">
// Освещение торгового зала
defineRule("lightingFrontshopOnOff", {
when: function() {
return dev._schedules.frontshop_lighting || true;
},
then: function (newValue, devName, cellName) {
log("lightingFrontshopOnOff newValue={}, devName={}, cellName={}", newValue, devName, cellName);
dev["wb-gpio/EXT1_R3A1"] = ! dev._schedules.frontshop_lighting; //инвертированный контактор
}
});
</div>
<div lang="ru" dir="ltr" class="mw-content-ltr">
// Вентиляция подсобного помещения
defineRule("ventBackstoreOnOff", {
when: function() {
return dev._schedules.ext_working_hours_15m || true;
},
then: function (newValue, devName, cellName) {
log("ventBackstoreOnOff newValue={}, devName={}, cellName={}", newValue, devName, cellName);
var on = dev._schedules.ext_working_hours_15m;
dev["wb-mr6c_81/K1"] = ! on; //инвертированный контактор
dev["wb-mr6c_81/K5"] = ! on; //инвертированный контактор
}
});
</div>
<div lang="ru" dir="ltr" class="mw-content-ltr">
// Освещение холодильных горок
defineRule("lightingCoolingshelfsOnOff", {
when: function() {
return dev._schedules.frontshop_lighting || true;
},
then: function (newValue, devName, cellName) {
log("lightingCoolingshelfsOnOff newValue={}, devName={}, cellName={}", newValue, devName, cellName);
var on = dev._schedules.working_hours_15m;
</div>
<div lang="ru" dir="ltr" class="mw-content-ltr">
// освещение в горках через нормально-закрытые реле (инвертировано)
dev["wb-mrm2-old_60/Relay 1"] = !on;
dev["wb-mrm2-old_61/Relay 1"] = !on;
dev["wb-mrm2-old_62/Relay 1"] = !on;
dev["wb-mrm2-old_63/Relay 1"] = !on;
dev["wb-mrm2-old_64/Relay 1"] = !on;
dev["wb-mrm2-old_65/Relay 1"] = !on;
dev["wb-mrm2-old_66/Relay 1"] = !on;
dev["wb-mrm2-old_67/Relay 1"] = !on;
}
});
</div>
<div lang="ru" dir="ltr" class="mw-content-ltr">
//Брендовые холодильники (пиво, лимонады)
defineRule("powerBrandFridgesOnOff", {
when: function() {
return dev._schedules.working_hours || true;
},
then: function (newValue, devName, cellName) {
log("powerBrandFridgesOnOff newValue={}, devName={}, cellName={}", newValue, devName, cellName);
var on = dev._schedules.working_hours;
dev["wb-gpio/EXT1_R3A5"] = !on; // инвертировано
}
});
</div>
<div lang="ru" dir="ltr" class="mw-content-ltr">
// ========= Котлы и приточная вентиляция ТЗ ===========
// обратная связь по температуре овощной зоны
</div>
<div lang="ru" dir="ltr" class="mw-content-ltr">
// днём работает позиционный регулятор
defineRule("heatersDayOff", {
when: function() {
return (dev._schedules.heaters_schedule) && (vegetablesTemperature > 17.0);
},
then: function (newValue, devName, cellName) {
log("heatersDayOff newValue={}, devName={}, cellName={}", newValue, devName, cellName);
heater1EnableInverted = !false; // инвертировано
}
});
</div>
<div lang="ru" dir="ltr" class="mw-content-ltr">
defineRule("heatersDayOn", {
when: function() {
return (dev._schedules.heaters_schedule) && (vegetablesTemperature < 16.7);
},
then: function (newValue, devName, cellName) {
log("heatersDayOn newValue={}, devName={}, cellName={}", newValue, devName, cellName);
heater1EnableInverted = !true; // инвертировано
}
});
</div>
<div lang="ru" dir="ltr" class="mw-content-ltr">
// ночью работает позиционный регулятор
defineRule("heatersNightOff", {
when: function() {
return (!dev._schedules.heaters_schedule) && (vegetablesTemperature > 11.6);
},
then: function (newValue, devName, cellName) {
log("heatersNightOff newValue={}, devName={}, cellName={}", newValue, devName, cellName);
heater1EnableInverted = !false; // инвертировано
}
});
</div>
<div lang="ru" dir="ltr" class="mw-content-ltr">
defineRule("heatersNightOn", {
when: function() {
return (!dev._schedules.heaters_schedule) && (vegetablesTemperature < 11.3);
},
then: function (newValue, devName, cellName) {
log("heatersNightOn newValue={}, devName={}, cellName={}", newValue, devName, cellName);
heater1EnableInverted = !true; // инвертировано
}
});
</div>
<div lang="ru" dir="ltr" class="mw-content-ltr">
// приточная и вытяжная вентиляция принудительно выключены
defineRule("ventFrontshopAlwaysOff", {
when: cron("@every 1m"),
then: function() {
dev["wb-gpio/EXT1_R3A3"] = !false;
dev["wb-gpio/EXT1_R3A4"] = !false;
}
});
// ================== Кассовая зона =================
</div>
<div lang="ru" dir="ltr" class="mw-content-ltr">
// в кассовой зоне в рабочее время температура поддерживается кондиционерами (позиционный регулятор)
</div>
<div lang="ru" dir="ltr" class="mw-content-ltr">
defineRule("countersACOn", {
when: function() {
return (dev._schedules.working_hours_15m) && (countersTemperature < 17.7);
},
then: function (newValue, devName, cellName) {
log("countersACOn newValue={}, devName={}, cellName={}", newValue, devName, cellName);
dev["wb-mir_75/Play from ROM7"] = true; // кондиционер кассовой зоны на нагрев
}
});
</div>
<div lang="ru" dir="ltr" class="mw-content-ltr">
// в нерабочее время кондиционер выключен
defineRule("countersACOff", {
when: function() {
return (!dev._schedules.working_hours_15m) || (countersTemperature > 18.0);
},
then: function (newValue, devName, cellName) {
log("countersACOff newValue={}, devName={}, cellName={}", newValue, devName, cellName);
dev["wb-mir_75/Play from ROM2"] = true; // кондиционер кассовой зоны выключить
}
});
</div>
<div lang="ru" dir="ltr" class="mw-content-ltr">
// =============== Овощная зона ==============
// Охлаждение овощей кондиционером только при температуре воздуха выше 18.5C
</div>
<div lang="ru" dir="ltr" class="mw-content-ltr">
defineRule("acVegOn", {
when: function() {
return vegetablesTemperature >= 18.5
},
then: function (newValue, devName, cellName) {
log("acVegOn newValue={}, devName={}, cellName={}", newValue, devName, cellName);
dev["wb-mir_76/Play from ROM3"] = true; // Охлаждение +18
}
});
</div>
<div lang="ru" dir="ltr" class="mw-content-ltr">
defineRule("acVegOff", {
when: function() {
return vegetablesTemperature < 17.8
},
then: function (newValue, devName, cellName) {
log("acVegOff newValue={}, devName={}, cellName={}", newValue, devName, cellName);
dev["wb-mir_76/Play from ROM2"] = true; // выключить
}
});
})()