translator, wb_editors
4444
правки
Matveevrj (обсуждение | вклад) |
|||
(не показано 46 промежуточных версий 6 участников) | |||
Строка 1: | Строка 1: | ||
<languages/> | <languages/> | ||
<translate> | <translate> | ||
=== Слежение за контролом | <!--T:255--> | ||
{{DISPLAYTITLE: Примеры правил}} | |||
== Общая информация == | |||
Здесь вы найдёте учебные примеры скриптов, написанных для движка правил '''[[wb-rules| wb-rules]]'''. | |||
Алгоритмы в примерах предельно просты и не учитывают многих факторов которые могут возникнуть в реальности. Поэтому используйте эту библиотеку только как учебный материал, а не источник готовых скриптов для реальных проектов. | |||
== Виртуальное устройство == | |||
Виртуальное устройство можно использовать для объединения каналов, задания особой логики для устройства или просто так для красоты. | |||
Пример ниже создаст виртуальное устройство с именем '''deviceName''' и двумя контролами '''value''' и '''state'''. А благодаря правилу с '''whenChanged''', значение контрола '''state''' будет менять в зависимости от значение контрола '''value'''. | |||
Виртуальным устройствам и контролам можно присваивать русские имена, задавая '''title''' в виде <code>title: { en: ’Title’, ru: ’Заголовок’ }</code>, или через '''setTitle''' у контрола: <code>setTitle({ en: ’Title’, ru: ’Заголовок’ })</code>. | |||
Для значений параметров с типом '''value''' и '''text''' можно использовать перечисления '''enum''' в виде набора именованных констант. Перечисления удобно использовать, когда значение параметра может принимать ограниченное количество значений, например, дни недели. | |||
Чтобы задать перечисление используйте для нужного контрола параметр '''enum''' с набором пар <code>“ключ”: “значение”</code>. | |||
Если параметр имеет тип '''value''' каждый ключ должен быть строковым числом в десятичном или шестнадцатеричном формате. | |||
<syntaxhighlight lang="ecmascript"> | |||
deviceName = 'my-virtual-device'; | |||
defineVirtualDevice(deviceName, { | |||
title: {'en': 'My Virtual Device', 'ru': 'Мое виртуальное устройство'} , | |||
cells: { | |||
value: { | |||
title: {'en': 'Value', 'ru': 'Значение'}, | |||
type: "range", | |||
value: 1, | |||
max: 3, | |||
min: 1 | |||
}, | |||
state: { | |||
title: {'en': 'State', 'ru': 'Состояние'}, | |||
type: "value", | |||
value: 1, | |||
enum:{ | |||
1: {'en': 'Normal', 'ru': 'В норме'}, | |||
2: {'en': 'Warning', 'ru': 'Внимание'}, | |||
3: {'en': 'Crash', 'ru': 'Авария'}} | |||
}, | |||
} | |||
}); | |||
defineRule({ | |||
whenChanged: deviceName+"/value", | |||
then: function (newValue, devName, cellName) { | |||
dev[deviceName+"/state"] = newValue; | |||
} | |||
}); | |||
</syntaxhighlight> | |||
== Слежение за контролом == <!--T:20--> | |||
<!--T:21--> | <!--T:21--> | ||
Строка 11: | Строка 67: | ||
<!--T:23--> | <!--T:23--> | ||
В примере датчик движения подключен к входу | В примере датчик движения подключен к входу «сухой контакт», контрол типа «switch». Сирена подключена к встроеному реле Wiren Board, а лампа — через релейный блок по Modbus. Когда вход типа «сухой контакт» (выход датчика движения) замкнут, то на лампу и реле подаётся «1», когда выключен — «0». | ||
<!--T:24--> | <!--T:24--> | ||
Правило срабатывает каждый раз при изменении значения контрола | Правило срабатывает каждый раз при изменении значения контрола «D1_IN» у устройства «wb-gpio». В код правила передаётся новое значение этого контрола в виде переменной newValue. | ||
<!--T:25--> | <!--T:25--> | ||
Строка 20: | Строка 76: | ||
<!--T:26--> | <!--T:26--> | ||
defineRule( | defineRule({ | ||
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; | ||
<!--T:27--> | <!--T:27--> | ||
Строка 52: | Строка 108: | ||
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; | ||
<!--T:32--> | <!--T:32--> | ||
Строка 60: | Строка 116: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
=== Детектор движения c таймаутом | == Мастер-выключатель с восстановлением последнего состояния == <!--T:250--> | ||
На вход контроллера подключен мастер-выключатель, который, при переключении, отключает все устройства, указанные в соответствующем правиле. При повторном нажатии на выключатель, устройствам возвращается первоначальное состояние. | |||
Подключение осуществляется к контакту A1 и 5V на контроллере. При замыкании на соответствующем канале <code>wb-gpio/A1_IN</code>, состояние меняется, и срабатывает правило. | |||
Для управления через веб-интерфейс создано виртуальное устройство, отображаемое на вкладке '''Устройства'''. | |||
Первоначальные состояния устройств сохраняются в [https://github.com/wirenboard/wb-rules#%D0%BF%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%BD%D0%BE%D0%B5-%D1%85%D1%80%D0%B0%D0%BD%D0%B8%D0%BB%D0%B8%D1%89%D0%B5 постоянном хранилище]. Переменные в постоянном хранилище записываются на флеш-память, что обеспечивает доступ к ним после перезагрузки контроллера. | |||
<syntaxhighlight lang="ecmascript"> | |||
defineVirtualDevice("power_off", { | |||
title: "Мастер-выключатель", | |||
cells: { | |||
power_off: { | |||
type: "pushbutton" | |||
}, | |||
} | |||
}); | |||
var ps = new PersistentStorage("power-storage", { global: true }); | |||
var lights = ["wb-mdm3_50/K1", "wb-mdm3_50/K2", "wb-mdm3_50/K3"]; | |||
var isPowerOff = true; | |||
defineRule({ | |||
whenChanged: ["wb-gpio/A1_IN", "power_off/power_off"], | |||
then: function (newValue, devName, cellName) { | |||
if (isPowerOff) { | |||
lights.forEach(function (light) { | |||
ps[light] = dev[light]; | |||
dev[light] = false; | |||
}); | |||
} else { | |||
lights.forEach(function (light) { | |||
dev[light] = ps[light]; | |||
}); | |||
} | |||
isPowerOff = !isPowerOff; | |||
} | |||
}); | |||
</syntaxhighlight> | |||
== Детектор движения c таймаутом == <!--T:34--> | |||
<!--T:35--> | <!--T:35--> | ||
На вход D2 подключен детектор движения с выходом | На вход D2 подключен детектор движения с выходом «сухой контакт». При обнаружении движения он замыкает D2 и GND, и на соответствующем канале <code>wb-gpio/D2_IN</code> появляется статус «1». | ||
<!--T:37--> | <!--T:37--> | ||
Строка 70: | Строка 169: | ||
<!--T:36--> | <!--T:36--> | ||
Правило работает так: | Правило работает так: | ||
* когда движение появляется, свет включается. Если ранее был запущен тридцатисекундный таймер | * когда движение появляется, свет включается. Если ранее был запущен тридцатисекундный таймер «на выключение», этот таймер отключается; | ||
* когда движение пропадает, запускается тридцатисекундный таймер | * когда движение пропадает, запускается тридцатисекундный таймер «на выключение». Если ему удаётся дойти до конца, свет выключается. | ||
<!--T:38--> | <!--T:38--> | ||
Строка 78: | Строка 177: | ||
var motion_timer_1_id = null; | var motion_timer_1_id = null; | ||
<!--T:191--> | |||
defineRule("motion_detector_1", { | defineRule("motion_detector_1", { | ||
whenChanged: "wb-gpio/D2_IN", | whenChanged: "wb-gpio/D2_IN", | ||
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); | ||
Строка 95: | Строка 195: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
== Создание однотипных правил == <!--T:43--> | |||
<!--T:44--> | <!--T:44--> | ||
Строка 108: | Строка 208: | ||
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); | ||
Строка 115: | Строка 215: | ||
<!--T:46--> | <!--T:46--> | ||
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); | ||
Строка 129: | Строка 229: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
== Активация правила только в определённое время == <!--T:48--> | |||
<!--T:49--> | <!--T:49--> | ||
Строка 160: | Строка 260: | ||
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) { | ||
Строка 167: | Строка 267: | ||
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); | ||
Строка 176: | Строка 276: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
== Роллеты == <!--T:53--> | |||
<!--T:54--> | <!--T:54--> | ||
Строка 209: | Строка 309: | ||
defineRule( "roller_shutter_up_on" + suffix, { | defineRule( "roller_shutter_up_on" + suffix, { | ||
asSoonAs: function() { | asSoonAs: function() { | ||
return dev[relay_up_device | return dev["relay_up_device/relay_up_control"]; | ||
}, | }, | ||
then: function () { | then: function () { | ||
Строка 218: | Строка 318: | ||
<!--T:59--> | <!--T:59--> | ||
relay_up_timer_id = setTimeout(function() { | relay_up_timer_id = setTimeout(function() { | ||
return dev[relay_up_device | return dev["relay_up_device/relay_up_control"] = 0; | ||
}, timeout_s * 1000); | }, timeout_s * 1000); | ||
} | } | ||
Строка 226: | Строка 326: | ||
defineRule("roller_shutter_down_on" + suffix, { | defineRule("roller_shutter_down_on" + suffix, { | ||
asSoonAs: function() { | asSoonAs: function() { | ||
return dev[relay_down_device | return dev["relay_down_device/relay_down_control"]; | ||
}, | }, | ||
then: function () { | then: function () { | ||
Строка 234: | Строка 334: | ||
relay_down_timer_id = setTimeout(function() { | relay_down_timer_id = setTimeout(function() { | ||
dev[relay_down_device | dev["relay_down_device/relay_down_control"] = 0; | ||
}, timeout_s * 1000); | }, timeout_s * 1000); | ||
} | } | ||
Строка 242: | Строка 342: | ||
defineRule("roller_shutter_both_on" + suffix, { | defineRule("roller_shutter_both_on" + suffix, { | ||
asSoonAs: function() { | asSoonAs: function() { | ||
return dev[relay_up_device | return dev["relay_up_device/relay_up_control"] && dev["relay_down_device/relay_down_control"]; | ||
}, | }, | ||
then: function () { | then: function () { | ||
Строка 257: | Строка 357: | ||
<!--T:63--> | <!--T:63--> | ||
dev[relay_up_device][relay_up_control] = 0; | dev[relay_up_device][relay_up_control] = 0; | ||
dev[relay_down_device | 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"); | ||
} | } | ||
Строка 311: | Строка 411: | ||
})(); | })(); | ||
</syntaxhighlight> | </syntaxhighlight> | ||
== Импульсные счетчики == | == Импульсные счетчики == <!--T:69--> | ||
<!--T:192--> | |||
Импульсный счетчик подключен к WB-MCM8. Выдает 1 импульс на 10 литров воды. При подключении на счетчике были показания 123.120 м³, что равно 123120 литрам воды. У WB-MCM8 при подключении было насчитано 7 импульсов. | Импульсный счетчик подключен к WB-MCM8. Выдает 1 импульс на 10 литров воды. При подключении на счетчике были показания 123.120 м³, что равно 123120 литрам воды. У WB-MCM8 при подключении было насчитано 7 импульсов. | ||
<!--T:193--> | |||
<syntaxhighlight lang="ecmascript"> | <syntaxhighlight lang="ecmascript"> | ||
var meterCorrection = 123120 // Корректировочное значение счетчика в литрах | var meterCorrection = 123120 // Корректировочное значение счетчика в литрах | ||
Строка 322: | Строка 423: | ||
var inpulseValue = 10 // Количество литров на один импульс | var inpulseValue = 10 // Количество литров на один импульс | ||
<!--T:194--> | |||
defineVirtualDevice("water_meters", { // Создаем виртуальный девайс для отображения в веб интерфейсе. | defineVirtualDevice("water_meters", { // Создаем виртуальный девайс для отображения в веб интерфейсе. | ||
title: "Счетчики воды", | title: "Счетчики воды", | ||
Строка 332: | Строка 434: | ||
}); | }); | ||
<!--T:195--> | |||
defineRule("water_meter_1", { | defineRule("water_meter_1", { | ||
whenChanged: "wb-mcm8_29/Input 1 counter", | whenChanged: "wb-mcm8_29/Input 1 counter", | ||
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; // Умножаем значение счетчика на количество литров/импульс и прибавляем корректировочное значение. | ||
} | } | ||
} | } | ||
Строка 342: | Строка 445: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
== Датчик MSW v.3 == | == Инвертирование значения контрола == | ||
[[Image:wb-rules-ex-buzzer-invert.png|300px|thumb|right|Пример устройств с вкладки Устройства]] | |||
Правило ниже создаёт виртуальное устройство ''my-invert-buzzer'', с контролом ''disabled'', который инвертирует состояние контрола ''enabled'' системной пищалки ''Buzzer''. | |||
<syntaxhighlight lang="ecmascript"> | |||
defineVirtualDevice('my-invert-buzzer', { | |||
title: 'Buzzer Invert' , | |||
cells: { | |||
Disabled: { | |||
title: "disabled", | |||
type: "switch", | |||
value: !dev["buzzer/enabled"] | |||
} | |||
} | |||
}) | |||
defineRule({ | |||
whenChanged: ["buzzer/enabled"], | |||
then: function(newValue, devName, cellName) { | |||
dev["my-invert-buzzer/Disabled"] = !newValue; | |||
} | |||
}); | |||
defineRule({ | |||
whenChanged: ["my-invert-buzzer/Disabled"], | |||
then: function(newValue, devName, cellName){ | |||
dev["buzzer/enabled"] = !newValue; | |||
} | |||
}); | |||
</syntaxhighlight> | |||
== Обработка счётчиков нажатий == <!--T:196--> | |||
{{Anchor|press-actions}} | |||
=== Описание === | |||
Последние версии прошивок устройств Wiren Board могут распознавать типы нажатий подключённых к входам кнопок и транслировать их по [[Modbus]] на контроллер Wiren Board. О том, как устройство распознаёт типы нажатий, читайте в его документации. | |||
<!--T:197--> | |||
Для обработки нажатий нужно отслеживать на контроллере состояние счётчика нужного типа нажатия и, при его изменении, выполнять действие. | |||
<!--T:198--> | |||
Обработку счётчиков удобно делать на [[wb-rules]], но вы можете использовать любой инструмент для автоматизации, например, [[Node-RED]]. Чтобы ускорить опрос счетчиков, настройте [[RS-485:Configuration via Web Interface#poll-period |период опроса]]. | |||
=== Примеры === <!--T:199--> | |||
{{YouTube | |||
|link=https://youtu.be/C60KB7TCeKg | |||
|text= Пример работы правила | |||
}} | |||
В примере мы используем модуль [[WB-MCM8 Modbus Count Inputs | WB-MCM8]] для управления первым каналом диммера [[WB-MDM3 230V Modbus Dimmer | WB-MDM3]]: | |||
# Короткое нажатие включает канал. | |||
# Двойное — выключает канал. | |||
# Длинное — увеличивает яркость. | |||
# Короткое, а затем длинное — уменьшает яркость. | |||
<!--T:200--> | |||
Так как изменение яркости требует растянутое во времени действие, то мы используем таймер. Также мы контролируем состояние входа с кнопкой и прекращаем действие при отпускании кнопки. | |||
<!--T:201--> | |||
<syntaxhighlight lang="js"> | |||
/* ---------------------------- */ | |||
/* 1. Single Press Counter: On action*/ | |||
/* ---------------------------- */ | |||
<!--T:202--> | |||
defineRule({ | |||
whenChanged: "wb-mcm8_20/Input 1 Single Press Counter", | |||
then: function (newValue, devName, cellName) { | |||
dev["wb-mdm3_58/K1"] = true; | |||
} | |||
}); | |||
<!--T:203--> | |||
/* ---------------------------- */ | |||
/* 2. Double Press Counter: Off action*/ | |||
/* ---------------------------- */ | |||
<!--T:204--> | |||
defineRule({ | |||
whenChanged: "wb-mcm8_20/Input 1 Double Press Counter", | |||
then: function (newValue, devName, cellName) { | |||
dev["wb-mdm3_58/K1"] = false; | |||
} | |||
}); | |||
<!--T:205--> | |||
/* --------------------------------------- */ | |||
/* 3. Long Press Counter: Increase brightness */ | |||
/* --------------------------------------- */ | |||
<!--T:206--> | |||
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); | |||
} | |||
}); | |||
<!--T:207--> | |||
// 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"]; | |||
<!--T:208--> | |||
if (i < 100 && dev["wb-mcm8_20/Input 1"]) { | |||
i++ | |||
dev["wb-mdm3_58/Channel 1"] = i | |||
} else { | |||
timers["input1_long_press"].stop(); | |||
} | |||
} | |||
}); | |||
<!--T:209--> | |||
/* -------------------------------------------- */ | |||
/* 4. Shortlong Press Counter: Decrease brightness */ | |||
/* -------------------------------------------- */ | |||
<!--T:210--> | |||
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); | |||
} | |||
}); | |||
<!--T:211--> | |||
// 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"]; | |||
<!--T:212--> | |||
if (i > 0 && dev["wb-mcm8_20/Input 1"]) { | |||
i-- | |||
dev["wb-mdm3_58/Channel 1"] = i | |||
} else { | |||
timers["input1_shortlong_press"].stop(); | |||
} | |||
} | |||
}); | |||
<!--T:213--> | |||
</syntaxhighlight> | |||
=== Универсальный модуль для wb-rules === <!--T:214--> | |||
Мы написали модуль для wb-rules [https://github.com/wirenboard/wb-community/tree/main/scripts/wb-press-actions wb-press-actions], который облегчает обработку нажатий в ваших скриптах. | |||
== Датчик MSW v.3 == <!--T:215--> | |||
<!--T:216--> | |||
При подключении датчика WB-MSW v.3 к контроллеру Wiren Board есть возможность создавать интересные сценарии, используя данные с датчика. На пример Включать свет по движению, сигнализировать светодиодами о превышении значения СО2 или VOC, Включать Кондиционер, если жарко или увлажнитель воздуха, если воздух слишком сухой. Правила создаются индивидуально под задачи. Здесь мы приведем несколько примеров для понимания принципа работы с датчиком. Больше примеров написания правил можно найти в документации '''[[Движок правил wb-rules]]'''. | При подключении датчика WB-MSW v.3 к контроллеру Wiren Board есть возможность создавать интересные сценарии, используя данные с датчика. На пример Включать свет по движению, сигнализировать светодиодами о превышении значения СО2 или VOC, Включать Кондиционер, если жарко или увлажнитель воздуха, если воздух слишком сухой. Правила создаются индивидуально под задачи. Здесь мы приведем несколько примеров для понимания принципа работы с датчиком. Больше примеров написания правил можно найти в документации '''[[Движок правил wb-rules]]'''. | ||
=== CO2 === | === CO2 === <!--T:217--> | ||
При концентрации CO2 меньше 650 - раз в 10 секунд мигаем зеленым. | При концентрации CO2 меньше 650 - раз в 10 секунд мигаем зеленым. | ||
<!--T:218--> | |||
При концентрации CO2 свыше 651, но меньше 1000 - раз в 5 секунд мигаем желтым. | При концентрации CO2 свыше 651, но меньше 1000 - раз в 5 секунд мигаем желтым. | ||
<!--T:219--> | |||
При концентрации CO2 свыше 1001 - раз в секунду мигаем красным. | При концентрации CO2 свыше 1001 - раз в секунду мигаем красным. | ||
<div class="mw-collapsible mw-collapsed"; style="width:600px; overflow: hidden;"> | <div class="mw-collapsible mw-collapsed"; style="width:600px; overflow: hidden;"> | ||
Строка 362: | Строка 621: | ||
if (co2_good) { | if (co2_good) { | ||
dev[devName | dev[devName+"/Green LED"] = true; | ||
dev[devName | dev[devName+"/Red LED"] = false; | ||
dev[devName | dev[devName+"/LED Period (s)"] = 10; | ||
} | } | ||
if (co2_middle) { | if (co2_middle) { | ||
dev[devName | dev[devName+"/Green LED"] = true; | ||
dev[devName | dev[devName+"/Red LED"] = true; | ||
dev[devName | dev[devName+"/LED Period (s)"] = 5; | ||
} | } | ||
if (co2_bad) { | if (co2_bad) { | ||
dev[devName | dev[devName+"/Green LED"] = false; | ||
dev[devName | dev[devName+"/Red LED"] = true; | ||
dev[devName | dev[devName+"/LED Period (s)"] = 1; | ||
} | } | ||
} | } | ||
}); | }); | ||
</syntaxhighlight> | </syntaxhighlight> | ||
Но когда устройств/правил много их целесообразно создавать одной функцией, передавая в нее разные параметры: | |||
<syntaxhighlight lang="ecmascript"> | |||
function ruleCO2 (devCO2, minCO2, maxCO2){ | |||
log.debug("rule create", devCO2) | |||
defineRule ("ruleCO2"+devCO2, { | |||
whenChanged: devCO2+"/CO2", | |||
then: function(newValue, devName, cellName) { | |||
log.info("ruleCO2 " + devCO2 +" enter with", newValue) | |||
if (newValue < minCO2) { | |||
dev[devCO2+"/LED Glow Duration (ms)"] = 50; | |||
dev[devCO2+"/Green LED"] = true; | |||
dev[devCO2+"/Red LED"] = false; | |||
dev[devCO2+"/LED Period (s)"] = 3; | |||
} | |||
if ((newValue > minCO2) && (newValue < maxCO2)) { | |||
dev[devCO2+"/LED Glow Duration (ms)"] = 50; | |||
dev[devCO2+"/Green LED"] = true; | |||
dev[devCO2+"/Red LED"] = true; | |||
dev[devCO2+"/LED Period (s)"] = 2; | |||
} | |||
if (newValue > maxCO2) { | |||
dev[devCO2+"/LED Glow Duration (ms)"] = 50; | |||
dev[devCO2+"/Green LED"] = false; | |||
dev[devCO2+"/Red LED"] = true; | |||
dev[devCO2+"/LED Period (s)"] = 1; | |||
} | |||
} | |||
}); | |||
} | |||
ruleCO2("wb-msw-v3_97", 650, 1000); | |||
ruleCO2("wb-msw-v3_98", 650, 1000); | |||
ruleCO2("wb-msw-v3_11", 500, 700); | |||
</syntaxhighlight> | |||
</div> | </div> | ||
=== Max Motion === | |||
=== Max Motion === <!--T:220--> | |||
"Max Motion" - максимальное значение датчика движения за N время. Время от 1 до 60 секунд можно выставить в 282 регистре. По умолчанию 10 секунд. При достижении Max Motion значения 50 проверяем достаточно ли освещена комната, если нет - включаем свет. Как только значение Max Motion упадет ниже 50 свет выключаем. | "Max Motion" - максимальное значение датчика движения за N время. Время от 1 до 60 секунд можно выставить в 282 регистре. По умолчанию 10 секунд. При достижении Max Motion значения 50 проверяем достаточно ли освещена комната, если нет - включаем свет. Как только значение Max Motion упадет ниже 50 свет выключаем. | ||
<!--T:221--> | |||
<div class="NavFrame"> | <div class="NavFrame"> | ||
<div class="NavContent"> | <div class="NavContent"> | ||
Строка 390: | Строка 689: | ||
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; | ||
} | } | ||
} | } | ||
Строка 404: | Строка 703: | ||
== Системные правила == <!--T:70--> | == Системные правила == <!--T:70--> | ||
<!--T:222--> | |||
Многие показания, которые видны в веб-интерфейсе контроллера из коробки, тоже создаются правилами на движке wb-rules. Их код находится здесь: [https://github.com/wirenboard/wb-rules-system https://github.com/wirenboard/wb-rules-system]. Системные правила собраны в пакет <code>wb-rules-system</code>, сами файлы скриптов на контроллере находятся в папке <code>/usr/share/wb-rules-system/</code>. | Многие показания, которые видны в веб-интерфейсе контроллера из коробки, тоже создаются правилами на движке wb-rules. Их код находится здесь: [https://github.com/wirenboard/wb-rules-system https://github.com/wirenboard/wb-rules-system]. Системные правила собраны в пакет <code>wb-rules-system</code>, сами файлы скриптов на контроллере находятся в папке <code>/usr/share/wb-rules-system/</code>. | ||
Строка 535: | Строка 835: | ||
whenChanged: "wb-adc/Vin", | whenChanged: "wb-adc/Vin", | ||
then: function() { | then: function() { | ||
if (dev["wb-adc"]["Vin"] < 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"] ; | ||
} | } | ||
} | } | ||
Строка 548: | Строка 848: | ||
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; | ||
} | } | ||
}); | }); | ||
Строка 558: | Строка 858: | ||
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; | ||
} | } | ||
}); | }); | ||
<!--T:98--> | <!--T:98--> | ||
</syntaxhighlight> | |||
== Термостат == <!--T:223--> | |||
Пример простого термостата из [https://support.wirenboard.com/t/novaya-versiya-dvizhka-pravil/4196/158 темы на портале поддержки]. | |||
<!--T:224--> | |||
<syntaxhighlight lang="js"> | |||
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, | |||
}, | |||
} | |||
}) | |||
<!--T:225--> | |||
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; | |||
} | |||
}); | |||
} | |||
<!--T:226--> | |||
Termostat("R01-TS16-1", "A60-M1W3/External Sensor 1", "Termostat/R01-TS16-1-setpoint", "wb-gpio/EXT4_R3A1", "Termostat/R01-TS16-1-onoff"); // Прихожая теплый пол | |||
</syntaxhighlight> | </syntaxhighlight> | ||
Строка 621: | Строка 973: | ||
<!--T:112--> | <!--T:112--> | ||
<pre> | <pre> | ||
nano /etc/wb-rules/rs485_cmd.js | |||
</pre> | </pre> | ||
Строка 645: | Строка 997: | ||
<!--T:116--> | <!--T:116--> | ||
<pre> | <pre> | ||
systemctl restart wb-rules | |||
</pre> | |||
<pre> | |||
journalctl -u wb-rules -f | |||
</pre> | </pre> | ||
Строка 746: | Строка 1100: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
== Отправка сообщения через Telegram-бота == | == Отправка сообщения через Telegram-бота == <!--T:227--> | ||
{{Anchor|telegram}} | |||
Сообщения отправляются с использованием [https://core.telegram.org/api#telegram-api Telegram API] через <code>curl</code>. | Сообщения отправляются с использованием [https://core.telegram.org/api#telegram-api Telegram API] через <code>curl</code>. | ||
<!--T:228--> | |||
<syntaxhighlight lang="ecmascript"> | <syntaxhighlight lang="ecmascript"> | ||
var message = "Text"; // напишите свой текст сообщения | var message = "Text"; // напишите свой текст сообщения | ||
Строка 755: | Строка 1111: | ||
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); | ||
<!--T:229--> | |||
runShellCommand(command); | runShellCommand(command); | ||
</syntaxhighlight> | </syntaxhighlight> | ||
== Обработка ошибок в работе с serial-устройствами == | == Обработка ошибок в работе с serial-устройствами == <!--T:230--> | ||
Реализована через подписку на все топики '''meta/error'''. | Реализована через подписку на все топики '''meta/error'''. | ||
<!--T:231--> | |||
<syntaxhighlight lang="ecmascript"> | <syntaxhighlight lang="ecmascript"> | ||
defineVirtualDevice("meta_error_test", { | defineVirtualDevice("meta_error_test", { | ||
Строка 779: | Строка 1137: | ||
<!--T:232--> | |||
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)) | ||
Строка 792: | Строка 1151: | ||
<!--T:233--> | |||
[[File:Sample-custom-config-1.png|300px|thumb|right|Пример конфигурации]] | [[File:Sample-custom-config-1.png|300px|thumb|right|Пример конфигурации]] | ||
[[File:Sample-custom-config-2.png|300px|thumb|right|Пример скрипта]] | [[File:Sample-custom-config-2.png|300px|thumb|right|Пример скрипта]] | ||
Строка 800: | Строка 1160: | ||
Но сложную настройку с меню и вариантами так не сделать. | Но сложную настройку с меню и вариантами так не сделать. | ||
<!--T:234--> | |||
Правильный, но сложный способ — создать новую вкладку в разделе Configs с редактируемыми полями параметров установок . | Правильный, но сложный способ — создать новую вкладку в разделе Configs с редактируемыми полями параметров установок . | ||
<!--T:235--> | |||
Потребуются три файла: | Потребуются три файла: | ||
<!--T:236--> | |||
1. Схема вывода html странички в разделе Configs : /usr/share/wb-mqtt-confed/schemas/test-config.schema.json | 1. Схема вывода html странички в разделе Configs : /usr/share/wb-mqtt-confed/schemas/test-config.schema.json | ||
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
Строка 811: | Строка 1174: | ||
"description":"Long description configuration", | "description":"Long description configuration", | ||
<!--T:237--> | |||
"configFile": { | "configFile": { | ||
"path":"/etc/test-config.conf", | "path":"/etc/test-config.conf", | ||
Строка 816: | Строка 1180: | ||
}, | }, | ||
<!--T:238--> | |||
"properties": { | "properties": { | ||
"temperature_setpoint": { | "temperature_setpoint": { | ||
Строка 826: | Строка 1191: | ||
}, | }, | ||
<!--T:239--> | |||
"humidity_setpoint": { | "humidity_setpoint": { | ||
"type":"number", | "type":"number", | ||
Строка 839: | Строка 1205: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
<!--T:240--> | |||
2. Описание конфигурации по умолчанию (при сохранении формы в веб интерфейсе, значения запишутся в этот файл) : /etc/test-config.conf | 2. Описание конфигурации по умолчанию (при сохранении формы в веб интерфейсе, значения запишутся в этот файл) : /etc/test-config.conf | ||
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
{ | { | ||
"temperature_setpoint": | "temperature_setpoint": 25, | ||
"humidity_setpoint": 14 | "humidity_setpoint": 14 | ||
} | } | ||
</syntaxhighlight> | </syntaxhighlight> | ||
<!--T:241--> | |||
3. Скрипт, обновляющий конфиг : /mnt/data/etc/wb-rules/test-config-script.js | 3. Скрипт, обновляющий конфиг : /mnt/data/etc/wb-rules/test-config-script.js | ||
<!--T:242--> | |||
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
var config = readConfig("/etc/test-config.conf"); | var config = readConfig("/etc/test-config.conf"); | ||
<!--T:243--> | |||
log("temperature setpoint is: {}".format(config.temperature_setpoint)); | log("temperature setpoint is: {}".format(config.temperature_setpoint)); | ||
</syntaxhighlight> | </syntaxhighlight> | ||
<!--T:244--> | |||
Последний файл можно в том числе редактировать из веб-интерфейса на вкладке Scripts. | Последний файл можно в том числе редактировать из веб-интерфейса на вкладке Scripts. | ||
<!--T:245--> | |||
В json файлах описаны схемы вывода html странички браузером, по общепринятому стандарту отображения. Описание ключей тут: json-schema.org. | В json файлах описаны схемы вывода html странички браузером, по общепринятому стандарту отображения. Описание ключей тут: json-schema.org. | ||
<!--T:246--> | |||
После создания файлов, нужно выполнить рестарт сервисов | После создания файлов, нужно выполнить рестарт сервисов | ||
<!--T:247--> | |||
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
service wb-mqtt-confed restart | service wb-mqtt-confed restart | ||
<!--T:248--> | |||
service wb-rules restart | service wb-rules restart | ||
</syntaxhighlight> | </syntaxhighlight> | ||
<!--T:249--> | |||
При нажатии кнопки Save в веб-интерфейсе, будет перезапускаться сервис wb-rules, а значения установок - записываться в правила. | При нажатии кнопки Save в веб-интерфейсе, будет перезапускаться сервис wb-rules, а значения установок - записываться в правила. | ||
Работает всё это через [https://github.com/wirenboard/json-editor json-editor]. | |||
== Сложные правила с расписаниями == <!--T:138--> | == Сложные правила с расписаниями == <!--T:138--> | ||
Строка 878: | Строка 1256: | ||
<!--T:141--> | <!--T:141--> | ||
Например, мы хотим, чтобы освещение было включено с 10 до 17ч. Обёртка (libschedule) будет выполнять правило | Например, мы хотим, чтобы освещение было включено с 10 до 17ч. Обёртка (libschedule) будет выполнять правило «включить освещение» раз в минуту с 10 утра до 17 вечера. | ||
<!--T:142--> | <!--T:142--> | ||
Строка 1290: | Строка 1668: | ||
}); | }); | ||
defineRule("acVegOff", { | |||
defineRule("acVegOff", { | |||
when: function() { | when: function() { | ||
return vegetablesTemperature < 17.8 | return vegetablesTemperature < 17.8 | ||
Строка 1302: | Строка 1679: | ||
})() | })() | ||
</syntaxhighlight> | </syntaxhighlight> | ||
== Полезные ссылки == | |||
==Работа с JSON== | |||
Движок wb-rules поддерживает стандартные функции языка JavaScript для работы с JSON: | |||
*<code>JSON.stringify()</code> — преобразует объект в JSON-строку; | |||
*<code>JSON.parse()</code> — преобразует JSON-строку в объект. | |||
Более подробную информацию о функциях можно найти в учебнике [https://learn.javascript.ru/json JavaScript]. | |||
Эти функции требуются, когда вы получаете данные из другого сервиса в JSON-формате. | |||
В приведенном примере создается виртуальное устройство с одной кнопкой и числовым параметром, который который хранится в виде JSON-строки. При нажатии на кнопку к значению параметра прибавляется 1. | |||
<syntaxhighlight lang="bash"> | |||
defineVirtualDevice("JSON_test", { | |||
title: "JSON_device", | |||
cells: { | |||
Button: { | |||
type: "pushbutton", | |||
value: false | |||
}, | |||
Json: { | |||
type : "text", | |||
value : JSON.stringify({param: 0}), | |||
}, | |||
} | |||
}); | |||
defineRule("change_value", { | |||
whenChanged: "JSON_test/Button", | |||
then: function () { | |||
parameter = JSON.parse(dev["JSON_test/Json"]); | |||
parameter.param++; | |||
dev["JSON_test/Json"] = JSON.stringify(parameter) | |||
} | |||
}); | |||
</syntaxhighlight> | |||
==Работа с последовательным портом через RPC== | |||
[[File:mqtt-rpc.png|300px|thumb|right|Работа с последовательным портом через RPC]] | |||
Если устройство на шине работает по протоколу, который не поддерживается драйвером [[Wb-mqtt-serial_driver |wb-mqtt-serial]] можно формировать запросы вручную и отправлять их драйверу через [https://github.com/wirenboard/mqtt-rpc RPC-MQTT]. | |||
RPC-MQTT создает MQTT-топик для отправки запросов, и топик для чтения ответов от драйвера. Поэтому для его использования достаточно отправить запрос в нужный топик функцией <code>publish()</code> и прочитать ответ функцией <code>trackMqtt()</code>. Как узнать адреса топиков описано в [https://github.com/wirenboard/mqtt-rpc документации]. | |||
В примере написан скрипт на wb-rules для отправки Modbus-запроса устройству Wiren Board на шине RS-485. | |||
Переменная <code>message</code> содержит Modbus-запрос, сформированный в соответствии со стандартом [[Modbus |Modbus RTU]]. | |||
Переменная <code>pathRPC</code> — это адрес MQTT-топика, в который отправляются запросы для драйвера wb-mqtt-serial. Для каждого сервиса используется свой топик, и узнать его адрес можно из документации на RPC-MQTT. | |||
<syntaxhighlight lang="bash"> | |||
var pathRPC = "/rpc/v1/wb-mqtt-serial/port/Load/"; //Адрес топика в который отправляется запрос | |||
var modbusPort = "/dev/ttyRS485-1"; | |||
var modbusSpeed = 9600; | |||
var modbusParity = "N"; | |||
var modbusStopbit = 2; | |||
var message = "E0300C8000644C9"; | |||
var clientID = "testRPC"; | |||
function requestRPC(modbusPort, modbusSpeed, modbusParity, modbusStopbit, clientID, requiestID, messageType, message, responseSize){ | |||
var strJson = JSON.stringify({params: {response_size: responseSize, format: messageType, path: modbusPort, baud_rate: modbusSpeed, parity: modbusParity, "data_bits" : 8, "stop_bits" : modbusStopbit, "msg": message}, "id" : requiestID}); | |||
log.info("strJson =", strJson); | |||
publish(pathRPC+clientID, strJson, 2, false) | |||
}; | |||
trackMqtt(pathRPC+clientID+"/reply", function(message){ | |||
log.info("name: {}, value: {}".format(message.topic, message.value)) | |||
}); | |||
requestRPC(modbusPort, modbusSpeed, modbusParity, modbusStopbit, clientID, 1, "HEX", message, 8) | |||
</syntaxhighlight> | |||
Если запрос отправлен без ошибок, то в лог будет выведено сообщение вида: | |||
<syntaxhighlight lang="bash"> | |||
name: /rpc/v1/wb-mqtt-serial/port/Load/testRPC/reply, | |||
value: {"error":null,"id":1,"result":{"response":"0e030400002569df"}} | |||
</syntaxhighlight> | |||
== Получение SMS == | |||
В примере с периодом в 1 секунду выводится в лог вся информация о последнем сообщении. Полученные SMS будут в capturedOutput. Пример из [https://support.wirenboard.com/t/wb7-modem-rabota-s-sms-soobshheniyami/18159 темы на портале]. | |||
<syntaxhighlight lang="bash"> | |||
var period = 1000; | |||
setInterval(function() { | |||
runShellCommand("mmcli --modem wbc --messaging-list-sms --output-keyvalue | grep length | cut -f2 -d':'", { | |||
captureOutput: true, | |||
exitCallback: function(exitCode, capturedOutput) { | |||
if (exitCode === 0) { | |||
runShellCommand("mmcli --modem wbc --sms " + (parseInt(capturedOutput) - 1).toString(), { | |||
captureOutput: true, | |||
exitCallback: function(exitCode, capturedOutput) { | |||
if (exitCode === 0) { | |||
log(capturedOutput); | |||
return; | |||
} | |||
} | |||
}); | |||
return; | |||
} | |||
} | |||
}); | |||
}, period); | |||
</syntaxhighlight> | |||
== Полезные ссылки == <!--T:190--> | |||
* [[Wb-rules | Краткое описание wb-rules на wiki]] | * [[Wb-rules | Краткое описание wb-rules на wiki]] | ||
* [https://github.com/wirenboard/wb-rules Полное описание wb-rules на Github] | * [https://github.com/wirenboard/wb-rules Полное описание wb-rules на Github] | ||
</translate> | </translate> |