217
правок
Gavrilov (обсуждение | вклад) |
Gavrilov (обсуждение | вклад) |
||
Строка 10: | Строка 10: | ||
== Совместимость скриптов при обновлении wb-rules == | |||
Предполагается, что при обновлении с предыдущей на следующую версию wb-rules и при соблюдении гайдлайнов при написании скриптов - все сценарии продолжат работать без каких-либо изменений. | |||
Далее перечислены возможные проблемы в связи с изменением логики обработки скриптов новыми версиями движка. | |||
=== Обновление до версии 2.2 === | |||
С версии 2.2 стали более строго проверяться типы устанавливаемых значений для контролов: | |||
если для установления признака включено/выключено для контролов типа switch, клики по pushbutton или присваивание значений контролам с типом text применялась недокументировання возможность | |||
использования для этой цели числовые значения (1, 0 и т.д.) в версии движка 2.2 операция присваивания не выполнится и завершится с ошибкой. | |||
Корректный способ — устанавливать булевы значений (true/false) для switch/pushbutton и строковые значения для типа text | |||
При возникновении подобной проблемы в логах можно видеть подобные записи: | |||
<pre> | |||
ERROR: control wb-mr3_30/K1 SetValue() error: can't convert control value '1' (type float64) to datatype 'switch' | |||
ERROR: control system/Reboot SetValue() error: can't convert control value '1' (type float64) to datatype 'pushbutton' | |||
ERROR: control status/someStatus SetValue() error: can't convert control value '-1.47' (type float64) to datatype 'text' | |||
</pre> | |||
Вместо: | |||
<syntaxhighlight lang="ecmascript"> | |||
dev["wb-mr3_30"]["K1"] = 1 // включение | |||
dev["status"]["someStatus"] = -1.47 // float | |||
</syntaxhighlight> | |||
Нужно: | |||
<syntaxhighlight lang="ecmascript"> | |||
dev["wb-mr3_30"]["K1"] = true // включение | |||
dev["status"]["someStatus"] = (-1.47).toString() // text | |||
</syntaxhighlight> | |||
=== Обновление до версии 1.7 === | |||
Начиная с версии wb-rules 1.7, локальные переменные и функции, объявленные в файле сценария не видны в других сценариях. | |||
Таким образом, каждый сценарий может определять свои функции и переменные без риска изменить поведение других сценариев. | |||
В качестве примера приведём два сценария, одновременно запускаемых в движке правил. Каждый сценарий определяет переменные и функции. | |||
В предыдущих версиях wb-rules обращение к переменной, изменяемой в нескольких файлах сценариев, может привести к неопределённому поведению. | |||
В версиях, начиная с 1.7, поведение строго определено и такое же, как будто сценарий единственный в системе. | |||
Сценарий 1 (rules1.js): | |||
<syntaxhighlight lang="ecmascript"> | |||
var test1 = 42; | |||
setInterval(function myFuncOne() { | |||
log("myFuncOne called"); | |||
log("test1: {}", test1); | |||
log("test2: {}", test2); | |||
}, 5000); | |||
</syntaxhighlight> | |||
Сценарий 2 (rules2.js): | |||
<syntaxhighlight lang="ecmascript"> | |||
var test1 = 84; | |||
var test2 = "Hello"; | |||
setInterval(function myFuncTwo() { | |||
log("myFuncTwo called"); | |||
log("test1: {}, test2: {}", test1, test2); | |||
}, 5000); | |||
</syntaxhighlight> | |||
Было: | |||
<pre> | |||
2019-01-05 17:29:50 myFuncTwo called | |||
2019-01-05 17:29:50 test1: 84, test2: Hello | |||
2019-01-05 17:29:50 myFuncOne called | |||
2019-01-05 17:29:50 test1: 84 | |||
2019-01-05 17:29:50 test2: Hello | |||
</pre> | |||
Стало: | |||
<pre> | |||
2019-01-05 17:28:42 myFuncTwo called | |||
2019-01-05 17:28:42 test1: 84, test2: Hello | |||
2019-01-05 17:28:42 myFuncOne called | |||
2019-01-05 17:28:42 test1: 42 | |||
2019-01-05 17:28:42 ECMAScript error: ReferenceError: identifier 'test2' undefined | |||
duk_js_var.c:1232 | |||
myFuncOne /etc/wb-rules/rules1.js:6 preventsyield | |||
</pre> | |||
Возможные способы решения проблемы: | |||
'''Способ 1''' | |||
Самый простой способ: перенести всю логику, которая использует доступ к переменной в другм файле в тот же файл, где определена переменная. Т.е. на примере приведённых выше правил нужно перенести всё в один файл rules1.js | |||
<syntaxhighlight lang="ecmascript"> | |||
var test1 = 42; | |||
var test2 = "Hello"; | |||
setInterval(function myFuncTwo() { | |||
log("myFuncTwo called"); | |||
log("test1: {}, test2: {}", test1, test2); | |||
}, 5000); | |||
setInterval(function myFuncOne() { | |||
log("myFuncOne called"); | |||
log("test1: {}", test1); | |||
log("test2: {}", test2); | |||
}, 5000); | |||
</syntaxhighlight> | |||
'''Способ 2''' | |||
Следующий способ заключается в использовании глобального постоянного хранилища (PersistentStorage) ==== | |||
'''Внимание:''' при использовании глобальных постоянных хранилищ может произойти совпадение имён, в этом случае возможно труднообнаруживаемое нарушение поведения. | |||
Вышеприведённый пример можно исправить следующим образом: | |||
Сценарий 1 (rules1.js): | |||
<syntaxhighlight lang="ecmascript"> | |||
var test1 = 42; | |||
var ps = new PersistentStorage("my-global-storage", {global: true}); | |||
ps.test1 = test1; | |||
setInterval(function myFuncOne() { | |||
log("myFuncOne called"); | |||
log("test1: {}", test1); | |||
log("test2: {}", ps.test2); | |||
}, 5000); | |||
</syntaxhighlight> | |||
Сценарий 2 (rules2.js): | |||
<syntaxhighlight lang="ecmascript"> | |||
var test2 = "Hello"; | |||
var ps = new PersistentStorage("my-global-storage", {global: true}); | |||
ps.test2 = test2; | |||
setInterval(function myFuncTwo() { | |||
log("myFuncTwo called"); | |||
log("test1: {}, test2: {}", ps.test1, test2); | |||
}, 5000); | |||
</syntaxhighlight> | |||
'''Способ 3''' | |||
"Грязный" способ, который использует прототип глобального объекта - использовать этот способ не рекомендуется из-за возможного замусоривания глобального пространства пользовательскими данными. | |||
Исправленная версия: | |||
Сценарий 1 (rules1.js): | |||
<syntaxhighlight lang="ecmascript"> | |||
global.__proto__.test1 = 42; | |||
setInterval(function myFuncOne() { | |||
log("myFuncOne called"); | |||
log("test1: {}", global.__proto__.test1); | |||
log("test2: {}", global.__proto__.test2); | |||
}, 5000); | |||
</syntaxhighlight> | |||
Сценарий 2 (rules2.js): | |||
<syntaxhighlight lang="ecmascript"> | |||
global.__proto__.test2 = "Hello"; | |||
setInterval(function myFuncTwo() { | |||
log("myFuncTwo called"); | |||
log("test1: {}, test2: {}", global.__proto__.test1, global.__proto__.test2); | |||
}, 5000); | |||
</syntaxhighlight> | |||
== Как создавать и редактировать правила == <!--T:3--> | == Как создавать и редактировать правила == <!--T:3--> | ||
Строка 94: | Строка 261: | ||
== | |||
=== Пишем сложные правила === <!--T:17--> | |||
<!--T:18--> | |||
Чтобы начать писать сложные правила, нужно посмотреть примеры правил и полную документацию по движку правил: | |||
#Примеры правил: | |||
#* на этой же странице ниже; | |||
#* в [http://forums.contactless.ru/t/dvizhok-pravil-primery-koda/483 специальной теме на нашем форуме ]. | |||
#[https://github.com/contactless/wb-rules Полное описание движка правил]. | |||
== Примеры правил == <!--T:19--> | |||
<!--T: | |||
=== | === Слежение за контролом === <!--T:20--> | ||
<!--T:21--> | <!--T:21--> | ||
Это простейшее правило следит за контролом и устанавливает другой контрол в такое же состояние. | |||
<!--T:22--> | <!--T:22--> | ||
Например правило может включать сирену и лампу, если датчик движения заметил движение. | |||
<!--T:23--> | <!--T:23--> | ||
В примере датчик движения подключен к входу "сухой контакт", контрол типа "switch". Сирена подключена к встроеному реле Wiren Board, а лампа - через релейный блок по Modbus. Когда вход типа "сухой контакт" (выход датчика движения) замкнут, то на лампу и реле подаётся "1", когда выключен - "0". | |||
<!--T:24--> | <!--T:24--> | ||
Правило срабатывает каждый раз при изменении значения контрола "D1_IN" у устройства "wb-gpio". В код правила передаётся новое значение этого контрола в виде переменной newValue. | |||
<!--T:25--> | <!--T:25--> | ||
<syntaxhighlight lang="ecmascript"> | |||
<!--T:26--> | <!--T:26--> | ||
defineRule("motion_detector", { | |||
whenChanged: "wb-gpio/D1_IN", | |||
then: function (newValue, devName, cellName) { | |||
dev["wb-gpio"]["Relay_2"] = newValue; | |||
dev["wb-mrm2_6"]["Relay 1"] = newValue; | |||
<!--T:27--> | |||
<!--T:27--> | } | ||
}); | |||
<!--T:28--> | <!--T:28--> | ||
</syntaxhighlight> | |||
<!--T:29--> | <!--T:29--> | ||
То же самое, но с виртуальным девайсом в качестве источника событий. Пример использования: сценарная кнопка, которая включает/выключает сирену и лампочку. | |||
<!--T:30--> | |||
<syntaxhighlight lang="ecmascript"> | |||
defineVirtualDevice("simple_test", { | |||
title: "Simple switch", | |||
cells: { | |||
enabled: { | |||
type: "switch", | |||
value: false | |||
}, | |||
} | |||
}); | |||
<!--T:31--> | <!--T:31--> | ||
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; | |||
<!--T:32--> | |||
} | |||
}); | |||
<!--T:33--> | <!--T:33--> | ||
</syntaxhighlight> | |||
=== Детектор движения c таймаутом === <!--T:34--> | |||
<!--T:35--> | <!--T:35--> | ||
На вход D2 подключен детектор движения с выходом типа "сухой контакт", который замыкает D2 и GND при обнаружении движения. | |||
При этом, на канале "wb-gpio/D2_IN" появляется статус "1". | |||
<!--T:36--> | |||
Правило включает свет при обнаружении движения и выключает свет, спустя 30 секунд после пропадания сигнала с датчика движения. | |||
<!--T:37--> | |||
= | Освещение подключено через встроенное реле, канал wb-gpio/Relay_1. | ||
<!--T:38--> | |||
<syntaxhighlight lang="ecmascript"> | |||
<!--T:39--> | |||
var motion_timer_1_timeout_ms = 30 * 1000; | |||
var motion_timer_1_id = null; | |||
<!--T:40--> | <!--T:40--> | ||
defineRule("motion_detector_1", { | |||
whenChanged: "wb-gpio/D2_IN", | |||
then: function (newValue, devName, cellName) { | |||
if (newValue) { | |||
dev["wb-gpio"]["Relay_1"] = 1; | |||
<!--T:41--> | |||
if (motion_timer_1_id) { | |||
clearTimeout(motion_timer_1_id); | |||
} | |||
motion_timer_1_id = setTimeout(function () { | |||
dev["wb-gpio"]["Relay_1"] = 0; | |||
motion_timer_1_id = null; | |||
}, motion_timer_1_timeout_ms); | |||
} | |||
} | |||
}); | |||
<!--T:42--> | |||
</syntaxhighlight> | |||
=== Создание однотипных правил === <!--T:43--> | |||
<!--T:44--> | |||
Если таких детекторов движения нужно несколько, то, чтобы не копировать код, можно обернуть создание правила и переменных в функцию: | |||
<!--T:45--> | |||
<syntaxhighlight lang="ecmascript"> | |||
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] = 1; | |||
if (motion_timer_id) { | |||
clearTimeout(motion_timer_id); | |||
} | |||
<!--T:46--> | |||
motion_timer_id = setTimeout(function() { | |||
dev["wb-gpio"][relay_control] = 0; | |||
motion_timer_id = null; | |||
}, timeout_ms); | |||
} | |||
} | |||
}); | |||
} | |||
<!--T:47--> | |||
makeMotionDetector("motion_detector_1", 20000, "EXT1_DR1", "EXT2_R3A1"); | |||
makeMotionDetector("motion_detector_2", 10000, "EXT1_DR2", "EXT2_R3A2"); | |||
makeMotionDetector("motion_detector_3", 10000, "EXT1_DR3", "EXT2_R3A3"); | |||
</syntaxhighlight> | |||
=== Активация правила только в определённое время === <!--T:48--> | |||
<!--T:49--> | |||
Правило как в предыдущем разделе, но выполняется только с 9:30 до 17:10 по UTC. | |||
<!--T:50--> | |||
<syntaxhighlight lang="ecmascript"> | <syntaxhighlight lang="ecmascript"> | ||
defineRule(" | var motion_timer_1_timeout_ms = 5 * 1000; | ||
var motion_timer_1_id = null; | |||
defineRule("motion_detector_1", { | |||
whenChanged: "wb-gpio/A1_IN", | whenChanged: "wb-gpio/A1_IN", | ||
then: function (newValue, devName, cellName) { | then: function (newValue, devName, cellName) { | ||
dev["wb- | var date = new Date(); | ||
<!--T:51--> | |||
// time point marking the beginning of the interval | |||
// i.e. "today, at HH:MM". All dates are in UTC! | |||
var date_start = new Date(date); | |||
date_start.setHours(9); | |||
date_start.setMinutes(30); | |||
<!--T:52--> | |||
// 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); | |||
} | |||
} | |||
} | |||
}); | }); | ||
</syntaxhighlight> | |||
=== Роллеты === <!--T:53--> | |||
<!--T:54--> | |||
Одно реле включает двигатель, поднимающий шторы, второе реле - включает двигатель, опускающий шторы. | |||
Правило следит за тем, чтобы оба реле не были включены одновременно. | |||
<!--T:55--> | |||
Кроме этого, правило отключает двигатели спустя заданное время после включения. | |||
<!--T:56--> | |||
<syntaxhighlight lang="ecmascript"> | |||
(function() { //don't touch this line | |||
var suffix = "1"; // must be different in different JS files | |||
var relay_up_device = "lc103_4"; | |||
var relay_up_control = "Relay 1"; | |||
<!--T:57--> | |||
var relay_down_device = "lc103_4"; | |||
var relay_down_control = "Relay 2"; | |||
<!--T:58--> | |||
var timeout_s = 15; | |||
// End of settings | |||
var relay_up_timer_id = null; | |||
var relay_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); | |||
}; | |||
<!--T:59--> | |||
relay_up_timer_id = setTimeout(function() { | |||
return dev[relay_up_device][relay_up_control] = 0; | |||
}, timeout_s * 1000); | |||
} | |||
}); | |||
<!--T:60--> | |||
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); | |||
} | |||
}); | |||
<!--T:61--> | |||
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); | |||
}; | |||
<!--T:62--> | |||
if (relay_down_timer_id) { | |||
relay_down_timer_id = clearTimeout(relay_down_timer_id); | |||
}; | |||
<!--T:63--> | |||
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"); | |||
} | |||
}); | |||
})(); | |||
</syntaxhighlight> | </syntaxhighlight> | ||
<!--T: | <!--T:64--> | ||
Более старая версия того же сценария демонстрирует использование alias-ов: | |||
<syntaxhighlight lang="ecmascript"> | <syntaxhighlight lang="ecmascript"> | ||
<!--T:65--> | |||
(function() { | |||
defineAlias("relay_up_1", "lc103_4/Relay 1"); | |||
defineAlias("relay_down_1", "lc103_4/Relay 2"); | |||
var timeout_s = 15; | |||
<!--T:66--> | |||
defineRule("roller_shutter_1_up_on", { | |||
asSoonAs: function() { | |||
return relay_up_1; | |||
}, | |||
then: function () { | |||
setTimeout(function() { | |||
relay_up_1 = 0; | |||
}, timeout_s * 1000); | |||
} | |||
}); | |||
<!--T:67--> | |||
defineRule("roller_shutter_1_down_on", { | |||
asSoonAs: function() { | |||
return relay_down_1; | |||
}, | |||
then: function () { | |||
setTimeout(function() { | |||
relay_down_1 = 0; | |||
}, timeout_s * 1000); | |||
} | |||
}); | |||
<!--T:68--> | |||
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"); | |||
} | |||
}); | |||
})(); | |||
<!--T:69--> | |||
</syntaxhighlight> | </syntaxhighlight> | ||
=== | === Системные правила === <!--T:70--> | ||
<!--T:71--> | |||
Некоторые правила поставляются с системой правил по умолчанию в пакете wb-rules-system. | |||
<!--T:72--> | |||
Полный список правил [https://github.com/contactless/wb-rules-system/tree/master/rules в репозитории]. | |||
<!--T:73--> | |||
Некоторые примеры: | |||
==== Правило для пищалки ==== <!--T:74--> | |||
<!--T:75--> | |||
[https://github.com/contactless/wb-rules-system/blob/master/rules/buzzer.js Правило] создаёт виртуальное устройство buzzer с ползунками для регулировки громкости и частоты, а также кнопкой включения звука. | |||
<!--T:76--> | |||
<syntaxhighlight lang="ecmascript"> | <syntaxhighlight lang="ecmascript"> | ||
defineVirtualDevice(" | defineVirtualDevice("buzzer", { | ||
title: " | title: "Buzzer", // | ||
cells: { | |||
" | <!--T:77--> | ||
type: " | cells: { | ||
frequency : { | |||
type : "range", | |||
value : 3000, | |||
max : 7000, | |||
}, | |||
volume : { | |||
type : "range", | |||
value : 10, | |||
max : 100, | |||
}, | |||
enabled : { | |||
type : "switch", | |||
value : false, | |||
}, | }, | ||
} | } | ||
}); | }); | ||
<!--T:78--> | |||
// setup pwm2 | |||
runShellCommand("echo 2 > /sys/class/pwm/pwmchip0/export"); | |||
then: function (newValue, devName, cellName) { | |||
dev | |||
<!--T:79--> | |||
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); | |||
<!--T:80--> | |||
runShellCommand("echo " + period + " > /sys/class/pwm/pwmchip0/pwm2/period"); | |||
runShellCommand("echo " + duty_cycle + " > /sys/class/pwm/pwmchip0/pwm2/duty_cycle"); | |||
}; | |||
<!--T:81--> | |||
defineRule("_system_buzzer_params", { | |||
whenChanged: [ | |||
"buzzer/frequency", | |||
"buzzer/volume", | |||
], | |||
<!--T:82--> | |||
then: function (newValue, devName, cellName) { | |||
if ( dev.buzzer.enabled) { | |||
_buzzer_set_params(); | |||
} | |||
} | } | ||
}); | }); | ||
defineRule({ | |||
<!--T:83--> | |||
defineRule("_system_buzzer_onof", { | |||
whenChanged: "buzzer/enabled", | |||
then: function (newValue, devName, cellName) { | then: function (newValue, devName, cellName) { | ||
dev | 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"); | |||
} | |||
} | |||
}); | }); | ||
<!--T:84--> | |||
</syntaxhighlight> | </syntaxhighlight> | ||
==== Правило для статуса питания ==== <!--T:85--> | |||
<!--T:86--> | |||
[https://github.com/contactless/wb-rules-system/blob/master/rules/power_status.js Правило] создаёт виртуальное устройство, которое сообщает текущий статус питания. В качестве входных данных используется два канала АЦП: измерение напряжения на аккумуляторе и измерение входного напряжения. | |||
<!--T:87--> | |||
Реализована следующая логика: | |||
<!--T:88--> | |||
1. Если входное напряжение меньше напряжение на аккумуляторе, то значит плата питается от аккумулятора. В этом случае, также отображается 0V в качестве входного напряжения. | |||
<!--T:89--> | |||
2. Если входное напряжение больше напряжения на аккумуляторе, то плата работает от внешнего источника питания. В качестве входонго напряжения отображается измерение с канала Vin. | |||
<!--T:90--> | |||
Для иллюстрации правила используют два разных способа срабатывания: по изменению значения контрола (правило _system_track_vin) и по изменению значения выражения (два других). | |||
<!--T:91--> | |||
<syntaxhighlight lang="ecmascript"> | <syntaxhighlight lang="ecmascript"> | ||
<!--T:92--> | |||
defineVirtualDevice("power_status", { | |||
title: "Power status", // | |||
<!--T:93--> | |||
cells: { | |||
'working on battery' : { | |||
type : "switch", | |||
value : false, | |||
readonly : true | |||
}, | |||
'Vin' : { | |||
type : "voltage", | |||
value : 0 | |||
} | |||
<!--T:94--> | |||
} | |||
}); | |||
< | <!--T:95--> | ||
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"] ; | |||
} | |||
} | } | ||
}); | |||
<!--T:96--> | |||
defineRule("_system_dc_on", { | |||
asSoonAs: function () { | |||
return dev["wb-adc"]["Vin"] > dev["wb-adc"]["BAT"]; | |||
}, | |||
then: function () { | |||
dev["power_status"]["working on battery"] = false; | |||
} | } | ||
}); | }); | ||
defineRule( | <!--T:97--> | ||
defineRule("_system_dc_off", { | |||
asSoonAs: function () { | |||
dev[" | return dev["wb-adc"]["Vin"] <= dev["wb-adc"]["BAT"]; | ||
}, | |||
then: function () { | |||
dev["power_status"]["working on battery"] = true; | |||
} | } | ||
}); | }); | ||
<!--T:98--> | |||
</syntaxhighlight> | </syntaxhighlight> | ||
=== Отправка команд по RS-485 === <!--T:99--> | |||
<!--T:100--> | |||
Для примера отправим команду устройству на порт ''/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 в документации]. | |||
<!--T:101--> | |||
С помощью движка правил создадим виртуальное устройство с контролом типа switch (переключатель). | |||
<!--T:102--> | |||
При включении переключателя будем отправлять команду (уст. Яркость кан. 00=0xff) для Uniel UCH-M141: | |||
<pre> | |||
FF FF 0A 01 FF 00 00 0A | |||
</pre> | |||
<!--T:103--> | |||
При выключении переключателя будем отправлять команду (уст. Яркость кан. 00=0x00) для Uniel UCH-M141: | |||
<pre> | |||
FF FF 0A 01 00 00 00 0B | |||
</pre> | |||
<!--T:104--> | |||
1. Настройка порта | |||
<!--T:105--> | |||
Для настройки порта /dev/ttyNSC0 на скорость 9600 надо выполнить следующую команду | |||
<!--T:106--> | |||
<pre> | |||
stty -F /dev/ttyNSC0 ospeed 9600 ispeed 9600 raw clocal -crtscts -parenb -echo cs8 | |||
</pre> | |||
<!--T:107--> | |||
2. Отправка команды | |||
<!--T:108--> | |||
Отправка данных делается следующей шелл-командой: | |||
<!--T:109--> | |||
<pre> | |||
/usr/bin/printf '\xFF\xFF\x0A\x01\xD1\x06\x00\xE2' >/dev/ttyNSC0 | |||
</pre> | |||
где "\xFF\xFF\x0A\x01\xD1\x06\x00\xE2" - это запись команды "FF FF 0A 01 D1 06 00 E2". | |||
<!--T:110--> | |||
3. Создадим в движке правил новый файл с правилами <code>/etc/wb-rules/rs485_cmd.js</code> | |||
<!--T:111--> | |||
Файл можно редактировать с помощью vim, nano или mcedit в сеансе ssh на устройстве, либо залить его с помощью SCP. | |||
<!--T:112--> | |||
<pre> | |||
root@wirenboard:~# mcedit /etc/wb-rules/rs485_cmd.js | |||
</pre> | |||
<!--T:113--> | |||
4. Описываем в файле виртуальный девайс | |||
<!--T:114--> | |||
<syntaxhighlight lang="ecmascript"> | <syntaxhighlight lang="ecmascript"> | ||
defineVirtualDevice(" | defineVirtualDevice("rs485_cmd", { | ||
title: "Send custom command to RS-485 port", | |||
cells: { | |||
enabled: { | |||
type: "switch", | |||
value: false | |||
}, | |||
} | } | ||
}); | }); | ||
</syntaxhighlight> | |||
<!--T:115--> | |||
5. Перезапускаем wb-rules и проверяем работу | |||
<!--T:116--> | |||
<pre> | |||
root@wirenboard:~# /etc/init.d/wb-rules restart | |||
root@wirenboard:~# tail -f /var/log/messages | |||
</pre> | |||
<!--T:117--> | |||
В логе не должно быть сообщений об ошибке (выход через control-c) | |||
<!--T:118--> | |||
В веб-интерфейсе в разделе Devices должно появиться новое устройство "Send custom command to RS-485 port". | |||
<!--T:119--> | |||
6. Добавим функцию для конфигурирования порта. | |||
<!--T:120--> | |||
<syntaxhighlight lang="ecmascript"> | |||
function setup_port() { | |||
runShellCommand("stty -F /dev/ttyNSC0 ospeed 9600 ispeed 9600 raw clocal -crtscts -parenb -echo cs8"); | |||
} | |||
<!--T:121--> | |||
</syntaxhighlight> | </syntaxhighlight> | ||
<!--T:122--> | |||
<!--T: | 7. Опишем правила на включение и выключение переключателя | ||
<!--T:123--> | |||
<syntaxhighlight lang="ecmascript"> | <syntaxhighlight lang="ecmascript"> | ||
defineRule("_rs485_switch_on", { | |||
asSoonAs: function () { | |||
return dev.rs485_cmd.enabled; | |||
enabled | }, | ||
then: function() { | |||
runShellCommand("/usr/bin/printf '\\xff\\xff\\x0a\\x01\\xff\\x00\\x00\\x0a' > /dev/ttyNSC0"); | |||
} | } | ||
}); | }); | ||
defineRule(" | <!--T:124--> | ||
defineRule("_rs485_switch_off", { | |||
asSoonAs: function () { | asSoonAs: function () { | ||
return dev | return !dev.rs485_cmd.enabled; | ||
}, | }, | ||
then: function () { | then: function() { | ||
runShellCommand("/usr/bin/printf '\\xff\\xff\\x0a\\x01\\x00\\x00\\x00\\x0b' >/dev/ttyNSC0"); | |||
} | } | ||
}); | }); | ||
<!--T:125--> | |||
</syntaxhighlight> | </syntaxhighlight> | ||
<!--T:126--> | |||
<!--T: | Обратите внимание на двойное экранирование. | ||
<!--T:127--> | |||
7. Собираем всё вместе | |||
<!--T:128--> | |||
Полное содержимое файла с правилами: | |||
<!--T:129--> | |||
<syntaxhighlight lang="ecmascript"> | <syntaxhighlight lang="ecmascript"> | ||
defineVirtualDevice(" | defineVirtualDevice("rs485_cmd", { | ||
title: "Send custom command to RS-485 port", | |||
cells: { | |||
enabled: { | |||
type: "switch", | |||
value: false | |||
}, | |||
} | } | ||
}); | }); | ||
defineRule(" | |||
<!--T:130--> | |||
function setup_port() { | |||
runShellCommand("stty -F /dev/ttyNSC0 ospeed 9600 ispeed 9600 raw clocal -crtscts -parenb -echo cs8"); | |||
} | |||
<!--T:131--> | |||
defineRule("_rs485_switch_on", { | |||
asSoonAs: function () { | asSoonAs: function () { | ||
return dev | return dev.rs485_cmd.enabled; | ||
}, | }, | ||
then: function () { | then: function() { | ||
runShellCommand("/usr/bin/printf '\\xff\\xff\\x0a\\x01\\xff\\x00\\x00\\x0a' > /dev/ttyNSC0"); | |||
} | } | ||
}); | }); | ||
defineRule(" | |||
<!--T:132--> | |||
then: function () { | 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"); | |||
} | } | ||
}); | }); | ||
<!--T:133--> | |||
setTimeout(setup_port, 1000); // запланировать выполнение setup_port() через 1 секунду после старта правил. | |||
<!--T:134--> | |||
</syntaxhighlight> | </syntaxhighlight> | ||
== | |||
<!--T: | === Пользовательские поля в интерфейсе === <!--T:135--> | ||
<!--T:136--> | |||
Чтобы дать пользователю возможность вводить точные значения параметров (уставки) из интерфейса, можно воспользоваться [https://wirenboard.com/wiki/index.php/%D0%A1%D0%BE%D0%B7%D0%B4%D0%B0%D0%BD%D0%B8%D0%B5_%D1%80%D0%B5%D0%B4%D0%B0%D0%BA%D1%82%D0%B8%D1%80%D1%83%D0%B5%D0%BC%D1%8B%D1%85_%D0%BF%D0%BE%D0%BB%D1%8C%D0%B7%D0%BE%D0%B2%D0%B0%D1%82%D0%B5%D0%BB%D1%8C%D1%81%D0%BA%D0%B8%D1%85_%D0%BF%D0%BE%D0%BB%D0%B5%D0%B9_%D0%B2_%D0%B2%D0%B5%D0%B1-%D0%B8%D0%BD%D1%82%D0%B5%D1%80%D1%84%D0%B5%D0%B9%D1%81%D0%B5 инструкцией]. | |||
<!--T:137--> | |||
Более подробно и с примером - в [https://support.wirenboard.com/t/kak-na-wb5-wb6-sozdat-pole-dlya-vvoda-ustavok-i-peredat-znachenie-v-pravila/2180 теме на портале техподдержки]. | |||
=== Импульсные счетчики === | |||
Импульсный счетчик подключен к WB-MCM8. Выдает 1 импульс на 10 литров воды. При подключении на счетчике были показания 123.120 м³, что равно 123120 литрам воды. У WB-MCM8 при подключении было насчитано 7 импульсов. | |||
<syntaxhighlight lang="ecmascript"> | <syntaxhighlight lang="ecmascript"> | ||
defineVirtualDevice(" | var meterCorrection = 123120 // Корректировочное значение счетчика в литрах | ||
var counterCorrection = 7 // Корректировочное значение WB-MCM8 в импульсах | |||
var inpulseValue = 10 // Количество литров на один импульс | |||
defineVirtualDevice("water_meters", { // Создаем виртуальный девайс для отображения в веб интерфейсе. | |||
title: "Счетчики воды", | |||
cells: { | |||
water_meter_1: { | |||
type: "value", | |||
value: 0 | |||
}, | |||
} | } | ||
}); | }); | ||
defineRule({ | 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; // Умножаем значение счетчика на количество литров/импульс и прибавляем корректировочное значение. | |||
} | |||
} | |||
}); | }); | ||
</syntaxhighlight> | </syntaxhighlight> | ||
=== Сложные правила с расписаниями === <!--T:138--> | |||
<!--T:139--> | |||
Объект - продуктовый магазин. Различные системы магазина управляются по обратной связи от датчиков температуры и с учётом расписания работы магазина. | |||
<!--T:140--> | |||
Для расписаний используются не cron-правила, а обёртка над ними. Обёртка включает и выключает правила, которые, в отличие от cron-правил, выполняются постоянно, будучи включенными. | |||
<!--T:141--> | |||
Например, мы хотим, чтобы освещение было включено с 10 до 17ч. Обёртка (libschedule) будет выполнять правило "включить освещение" раз в минуту с 10 утра до 17 вечера. | |||
<!--T:142--> | |||
Это значит, что даже если контроллер работает с перерывами и пропустил время перехода между расписаниями (10 утра), то контроллер всё равно включит освещение при первой возможности. | |||
<!--T:143--> | |||
lib_schedules.js: | |||
<!--T:144--> | |||
<syntaxhighlight lang="ecmascript"> | <syntaxhighlight lang="ecmascript"> | ||
global.__proto__.Schedules = {}; | |||
<!--T:145--> | |||
(function(Schedules) { // замыкание | |||
<!--T:146--> | |||
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; | |||
} | |||
<!--T:147--> | |||
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)); | |||
<!--T:148--> | |||
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) | |||
<!--T:149--> | |||
// 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; | |||
} | |||
<!--T:150--> | |||
// well, that seems not to be the case. ok, | |||
// option 2: it's the day of "start" date: | |||
<!--T:151--> | |||
if (now >= start_date) { | |||
return true; | |||
} | |||
} | |||
return false; | |||
<!--T:152--> | |||
} | |||
<!--T:153--> | |||
function checkSchedule(schedule, now) { | |||
if (now == undefined) { | |||
now = new Date(); | |||
} | |||
<!--T:154--> | |||
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); | |||
}; | |||
<!--T:155--> | |||
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); | |||
} | |||
}); | |||
} | |||
} | |||
} | } | ||
<!--T:156--> | |||
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]; | |||
} | } | ||
}); | |||
} | } | ||
<!--T:157--> | |||
var _schedules = {}; | |||
<!--T:158--> | |||
Schedules.registerSchedule = function(schedule) { | |||
_schedules[schedule.name] = schedule; | |||
}; | |||
<!--T:159--> | |||
Schedules.initSchedules = function() { | |||
var params = { | |||
title: "Schedule Status", | |||
cells: {} | |||
}; | |||
<!--T:160--> | |||
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}; | |||
} | |||
}; | |||
<!--T:161--> | |||
defineVirtualDevice("_schedules", params); | |||
<!--T:162--> | |||
for (var schedule_name in _schedules) { | |||
if (_schedules.hasOwnProperty(schedule_name)) { | |||
var schedule = _schedules[schedule_name]; | |||
<!--T:163--> | |||
// setup cron tasks which updates the schedule dev status at schedule | |||
// interval beginings and ends | |||
addScheduleDevCronTasks(schedule); | |||
<!--T:164--> | |||
// if needed, setup periodic task to trigger rules which use this schedule | |||
if (schedule.autoUpdate) { | |||
addScheduleAutoUpdCronTask(schedule); | |||
} | |||
<!--T:165--> | |||
// set schedule dev status as soon as possible at startup | |||
(function(schedule) { | |||
setTimeout(function() { | |||
updateSingleScheduleDevStatus(schedule); | |||
}, 1); | |||
})(schedule); | |||
<!--T:166--> | |||
}; | |||
}; | |||
< | <!--T:167--> | ||
}; | |||
<!--T:168--> | |||
})(Schedules); | |||
</syntaxhighlight> | </syntaxhighlight> | ||
<!--T:169--> | |||
Пример правил, с использованием Schedules: | |||
<syntaxhighlight lang="ecmascript"> | <syntaxhighlight lang="ecmascript"> | ||
(function() { // замыкание | |||
<!--T:170--> | |||
defineAlias("countersTemperature", "wb-msw2_30/Temperature"); | |||
defineAlias("vegetablesTemperature", "wb-msw2_31/Temperature"); | |||
<!--T:171--> | |||
defineAlias("heater1EnableInverted", "wb-mrm2-old_70/Relay 1"); | |||
defineAlias("frontshopVentInverted", "wb-gpio/EXT1_R3A3"); | |||
<!--T:172--> | |||
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(); | |||
<!--T:173--> | |||
// Вывеска и фасадное освещение | |||
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; | |||
} | |||
}); | |||
<!--T:174--> | |||
// Освещение торгового зала | |||
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; //инвертированный контактор | |||
} | |||
}); | |||
<!--T:175--> | |||
< | // Вентиляция подсобного помещения | ||
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; //инвертированный контактор | |||
} | |||
}); | |||
<!--T:176--> | |||
< | // Освещение холодильных горок | ||
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; | |||
<!--T:177--> | |||
// освещение в горках через нормально-закрытые реле (инвертировано) | |||
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; | |||
} | |||
}); | |||
<!--T:178--> | |||
//Брендовые холодильники (пиво, лимонады) | |||
var | 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; // инвертировано | |||
} | |||
}); | |||
<!--T:179--> | |||
// ========= Котлы и приточная вентиляция ТЗ =========== | |||
// обратная связь по температуре овощной зоны | |||
<!--T:180--> | |||
// днём работает позиционный регулятор | |||
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; // инвертировано | |||
} | |||
}); | |||
<!--T:181--> | |||
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; // инвертировано | |||
} | |||
}); | |||
<!--T:182--> | |||
// ночью работает позиционный регулятор | |||
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; // инвертировано | |||
} | |||
}); | |||
<!--T:183--> | |||
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; // инвертировано | |||
} | |||
}); | |||
<!--T:184--> | |||
// приточная и вытяжная вентиляция принудительно выключены | |||
defineRule("ventFrontshopAlwaysOff", { | |||
when: cron("@every 1m"), | |||
then: function() { | |||
dev["wb-gpio/EXT1_R3A3"] = !false; | |||
dev["wb-gpio/EXT1_R3A4"] = !false; | |||
} | |||
}); | |||
// ================== Кассовая зона ================= | |||
<!--T:185--> | |||
// в кассовой зоне в рабочее время температура поддерживается кондиционерами (позиционный регулятор) | |||
<!--T:186--> | |||
defineRule("countersACOn", { | |||
log(" | 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; // кондиционер кассовой зоны на нагрев | |||
} | |||
}); | |||
<!--T:187--> | |||
// в нерабочее время кондиционер выключен | |||
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; // кондиционер кассовой зоны выключить | |||
} | |||
}); | |||
<!--T:188--> | |||
// =============== Овощная зона ============== | |||
// Охлаждение овощей кондиционером только при температуре воздуха выше 18.5C | |||
<!--T:189--> | |||
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 | |||
} | |||
}); | |||
<!--T:190--> | |||
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; // выключить | |||
} | |||
}); | |||
})() | |||
</syntaxhighlight> | </syntaxhighlight> | ||
== Полное описание возможностей движка правил == <!--T:191--> | |||
<!--T:192--> | |||
Самое полное описание движка правил: https://github.com/contactless/wb-rules/blob/master/README.md | |||
== Новые возможности последних версий == <!--T:193--> | |||
<!--T:194--> | |||
* [[Special:MyLanguage/Движок_правил_wb-rules_1.7|Движок правил wb-rules 1.7]] | |||
== В разработке == <!--T:195--> | |||
<!--T:196--> | |||
Описание возможностей будущих версий движка правил можно прочесть здесь: | |||
* [[Special:MyLanguage/Движок_правил_wb-rules_2.0|Движок правил wb-rules 2.0]] | |||
</translate> | </translate> |
правок