16 885
правок
(не показаны 23 промежуточные версии 6 участников) | |||
Строка 1: | Строка 1: | ||
<languages/> | <languages/> | ||
<translate> | <translate> | ||
<!--T: | <!--T:255--> | ||
{{DISPLAYTITLE: Примеры правил}} | {{DISPLAYTITLE: Примеры правил}} | ||
=== Слежение за контролом | == Общая информация == | ||
Здесь вы найдёте учебные примеры скриптов, написанных для движка правил '''[[wb-rules| wb-rules]]'''. | |||
Алгоритмы в примерах предельно просты и не учитывают многих факторов которые могут возникнуть в реальности. Поэтому используйте эту библиотеку только как учебный материал, а не источник готовых скриптов для реальных проектов. | |||
== Виртуальное устройство == | |||
Виртуальное устройство можно использовать для объединения каналов, задания особой логики для устройства или просто так для красоты. | |||
Пример ниже создаст виртуальное устройство с именем '''deviceName''' и двумя контролами '''value''' и '''state'''. А благодаря правилу с '''whenChanged''', значение контрола '''state''' будет менять в зависимости от значение контрола '''value'''. | |||
<syntaxhighlight lang="ecmascript"> | |||
deviceName = 'my-virtual-device'; | |||
defineVirtualDevice(deviceName, { | |||
title: {'en': 'My Virtual Device', 'ru': 'Мое виртуальное устройство'} , | |||
cells: { | |||
value: { | |||
title: {'en': 'Value', 'ru': 'Значение'}, | |||
type: "range", | |||
value: 1, | |||
max: 3, | |||
min: 1 | |||
}, | |||
state: { | |||
title: {'en': 'State', 'ru': 'Состояние'}, | |||
type: "value", | |||
value: 1, | |||
enum:{ | |||
1: {'en': 'Normal', 'ru': 'В норме'}, | |||
2: {'en': 'Warning', 'ru': 'Внимание'}, | |||
3: {'en': 'Crash', 'ru': 'Авария'}} | |||
}, | |||
} | |||
}); | |||
defineRule({ | |||
whenChanged: deviceName+"/value", | |||
then: function (newValue, devName, cellName) { | |||
dev[deviceName+"/state"] = newValue; | |||
} | |||
}); | |||
</syntaxhighlight> | |||
== Слежение за контролом == <!--T:20--> | |||
<!--T:21--> | <!--T:21--> | ||
Строка 55: | Строка 100: | ||
then: function (newValue, devName, cellName) { | then: function (newValue, devName, cellName) { | ||
dev["wb-gpio/Relay_2"] = newValue; | dev["wb-gpio/Relay_2"] = newValue; | ||
dev["wb-mrm2_6/ | dev["wb-mrm2_6/Relay 1"] = newValue; | ||
<!--T:32--> | <!--T:32--> | ||
Строка 62: | Строка 107: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
== | == Мастер-выключатель с восстановлением последнего состояния == <!--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"> | <syntaxhighlight lang="ecmascript"> | ||
var | defineVirtualDevice("power_off", { | ||
var | 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({ | |||
defineRule( | whenChanged: ["wb-gpio/A1_IN", "power_off/power_off"], | ||
whenChanged: "wb-gpio/ | |||
then: function (newValue, devName, cellName) { | then: function (newValue, devName, cellName) { | ||
if ( | if (isPowerOff) { | ||
dev[ | lights.forEach(function (light) { | ||
ps[light] = dev[light]; | |||
dev[light] = false; | |||
}); | |||
} else { | |||
dev[ | lights.forEach(function (light) { | ||
dev[light] = ps[light]; | |||
} | }); | ||
} | } | ||
} | isPowerOff = !isPowerOff; | ||
} | |||
}); | }); | ||
</syntaxhighlight> | </syntaxhighlight> | ||
== | == Детектор движения c таймаутом == <!--T:34--> | ||
<!--T: | <!--T:35--> | ||
Если | На вход D2 подключен детектор движения с выходом «сухой контакт». При обнаружении движения он замыкает D2 и GND, и на соответствующем канале <code>wb-gpio/D2_IN</code> появляется статус «1». | ||
<!--T:37--> | |||
Освещение подключено через встроенное реле, соответствующий канал <code>wb-gpio/Relay_1</code>. | |||
<!--T:36--> | |||
Правило работает так: | |||
* когда движение появляется, свет включается. Если ранее был запущен тридцатисекундный таймер «на выключение», этот таймер отключается; | |||
* когда движение пропадает, запускается тридцатисекундный таймер «на выключение». Если ему удаётся дойти до конца, свет выключается. | |||
<!--T: | <!--T:38--> | ||
<syntaxhighlight lang="ecmascript"> | <syntaxhighlight lang="ecmascript"> | ||
var motion_timer_1_timeout_ms = 30 * 1000; | |||
var motion_timer_1_id = null; | |||
<!--T:191--> | |||
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); | |||
} | |||
}, | |||
}); | |||
</syntaxhighlight> | |||
<!--T: | == Создание однотипных правил == <!--T:43--> | ||
<!--T:44--> | |||
Если таких детекторов движения нужно несколько, то, чтобы не копировать код, можно обернуть создание правила и переменных в функцию: | |||
<!--T: | <!--T:45--> | ||
<syntaxhighlight lang="ecmascript"> | <syntaxhighlight lang="ecmascript"> | ||
function makeMotionDetector(name, timeout_ms, detector_control, relay_control) { | |||
var | var motion_timer_id = null; | ||
defineRule(name, { | |||
defineRule( | whenChanged: "wb-gpio/" + detector_control, | ||
then: function(newValue, devName, cellName) { | |||
if (!newValue) { | |||
dev["wb-gpio/relay_control"] = true; | |||
if (motion_timer_id) { | |||
clearTimeout(motion_timer_id); | |||
} | |||
<!--T:46--> | |||
motion_timer_id = setTimeout(function() { | |||
dev["wb-gpio/relay_control"] = false; | |||
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:52--> | <!--T:49--> | ||
// time point marking the end of the interval | Правило как в предыдущем разделе, но выполняется только с 9:30 до 17:10 по UTC. | ||
var date_end = new Date(date); | |||
date_end.setHours(17); | <!--T:50--> | ||
date_end.setMinutes(10); | <syntaxhighlight lang="ecmascript"> | ||
var motion_timer_1_timeout_ms = 5 * 1000; | |||
var motion_timer_1_id = null; | |||
defineRule("motion_detector_1", { | |||
whenChanged: "wb-gpio/A1_IN", | |||
then: function (newValue, devName, cellName) { | |||
var date = new Date(); | |||
<!--T: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 time is between 09:30 and 17:10 UTC | ||
if ((date > date_start) && (date < date_end)) { | if ((date > date_start) && (date < date_end)) { | ||
Строка 179: | Строка 267: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
== Роллеты == <!--T:53--> | |||
<!--T:54--> | <!--T:54--> | ||
Строка 344: | Строка 432: | ||
dev["water_meters/water_meter_1"] = ((parseInt(newValue) - counterCorrection) * inpulseValue) + meterCorrection; // Умножаем значение счетчика на количество литров/импульс и прибавляем корректировочное значение. | dev["water_meters/water_meter_1"] = ((parseInt(newValue) - counterCorrection) * inpulseValue) + meterCorrection; // Умножаем значение счетчика на количество литров/импульс и прибавляем корректировочное значение. | ||
} | } | ||
} | |||
}); | |||
</syntaxhighlight> | |||
== Инвертирование значения контрола == | |||
[[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; | |||
} | } | ||
}); | }); | ||
Строка 494: | Строка 612: | ||
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; | ||
} | } | ||
} | } | ||
Строка 763: | Строка 881: | ||
type: "switch", | type: "switch", | ||
value: false, | value: false, | ||
}, | }, | ||
} | |||
}) | |||
<!--T:225--> | <!--T:225--> | ||
Строка 790: | Строка 910: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
== Отправка команд по RS-485 == <!--T:99--> | == Отправка команд по RS-485 == <!--T:99--> | ||
Строка 1079: | Строка 1200: | ||
<syntaxhighlight lang="bash"> | <syntaxhighlight lang="bash"> | ||
{ | { | ||
"temperature_setpoint": | "temperature_setpoint": 25, | ||
"humidity_setpoint": 14 | "humidity_setpoint": 14 | ||
} | } | ||
Строка 1547: | Строка 1668: | ||
})() | })() | ||
</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--> | == Полезные ссылки == <!--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> |