Hi,
I’ve been playing with the “experimental” rules engine lately, to see if I could migrate most of my DSL rules to it (they are pretty simple). It’s working OK so far, and I think it has a lot of potential, especially with the JSR 223 scripting support, but the Paper UI interface to design them was IMHO not great - lots of clicks, dialog boxes, unneeded animations and so on. So I went ahead and forked HABPanel to make my own IDE … it’s still good old AngularJS 1.x, not the fancy new frameworks, but staying with that technology stack allowed me to borrow a lot from cool libraries like ngFlowchart as well as HABPanel assets (environment, services and directives like the item picker etc.) to reduce the development effort to a minimum.
Next I wanted to challenge the rules engine to see if the rules could be “chained” with the “run rules” action to have some sort of control flow. This worked well so I decided to allow that in my app, and make it build several rules from the same flow and link them automatically. Writing the “compilation” algorithm was a fun challenge - but it’s far from perfect.
The result is Flows Builder (sorry about my lack of imagination for the name).
I’ve decided to package it properly and release it as it’s pretty useable already in case anyone wants to give it a try. Download the binary here and follow the instructions in the readme at GitHub.
It’s by no means complete or production-ready, so consider yourselves warned
Flowcharts are saved in the service configuration independently of rules, and and then published (or deployed) to the rules engine. Think of them as “source code” which is then compiled into one or more rules that can be executed. For instance, here’s the build result of the example above:
[
{
"uid": "example_flow_1",
"name": "example_flow - 1: When Door_Opened changed from OFF to ON",
"description": "Created with Flows Builder - will be overwritten if the workflow is published again",
"visibility": "VISIBLE",
"enabled": true,
"triggers": [
{
"id": 1,
"label": "When Door_Opened changed from OFF to ON",
"configuration": {
"itemName": "Door_Opened",
"previousState": "OFF",
"state": "ON"
},
"type": "core.ItemStateChangeTrigger"
}
],
"conditions": [],
"actions": [
{
"id": 2,
"label": "Send command ON to Patio_Lights",
"inputs": {},
"configuration": {
"itemName": "Patio_Lights",
"command": "ON"
},
"type": "core.ItemCommandAction"
},
{
"id": 3,
"label": "Proceed to condition: If Away_Mode = ON",
"description": "Run next rules: example_flow_1_3 & example_flow_1_3neg",
"inputs": {},
"configuration": {
"considerConditions": true,
"ruleUIDs": [
"example_flow_1_3",
"example_flow_1_3neg"
]
},
"type": "core.RunRuleAction"
}
]
},
{
"uid": "example_flow_1_3",
"name": "If Away_Mode = ON",
"description": "Created by Flows Builder for the \"then\" path of a condition. Do not run separately, will be overwritten if the flow is published again.",
"visibility": "VISIBLE",
"enabled": true,
"triggers": [],
"conditions": [
{
"id": 1,
"label": "If Away_Mode = ON",
"inputs": {},
"configuration": {
"itemName": "Away_Mode",
"operator": "=",
"state": "ON"
},
"type": "core.ItemStateCondition"
}
],
"actions": [
{
"id": 2,
"label": "Say \"Intrusion detected!\"",
"inputs": {},
"configuration": {
"text": "Intrusion detected!",
"sink": "webaudio"
},
"type": "media.SayAction"
},
{
"id": 3,
"label": "Play barking.mp3",
"inputs": {},
"configuration": {
"sound": "barking.mp3",
"sink": "webaudio"
},
"type": "media.PlayAction"
},
{
"id": 4,
"label": "Report intrusion",
"inputs": {},
"configuration": {
"type": "application/javascript",
"script": "print('Intrusion report');"
},
"type": "script.ScriptAction"
}
]
},
{
"uid": "example_flow_1_3neg",
"name": "NOT(If Away_Mode = ON)",
"description": "Created by Flows Builder for the \"else\" path of a condition. Do not run separately, will be overwritten if the flow is published again.",
"visibility": "VISIBLE",
"enabled": true,
"triggers": [],
"conditions": [
{
"id": 1,
"label": "NOT(If Away_Mode = ON)",
"inputs": {},
"configuration": {
"itemName": "Away_Mode",
"operator": "!=",
"state": "ON"
},
"type": "core.ItemStateCondition"
}
],
"actions": [
{
"id": 2,
"label": "Proceed to condition: If the day is SAT,SUN",
"description": "Run next rules: example_flow_1_3neg_7 & example_flow_1_3neg_7neg",
"inputs": {},
"configuration": {
"considerConditions": true,
"ruleUIDs": [
"example_flow_1_3neg_7",
"example_flow_1_3neg_7neg"
]
},
"type": "core.RunRuleAction"
}
]
},
{
"uid": "example_flow_1_3neg_7",
"name": "If the day is SAT,SUN",
"description": "Created by Flows Builder for the \"then\" path of a condition. Do not run separately, will be overwritten if the flow is published again.",
"visibility": "VISIBLE",
"enabled": true,
"triggers": [],
"conditions": [
{
"id": 1,
"label": "If the day is SAT,SUN",
"inputs": {},
"configuration": {
"days": [
"SAT",
"SUN"
]
},
"type": "timer.DayOfWeekCondition"
}
],
"actions": [
{
"id": 2,
"label": "Start weekend playlist",
"inputs": {},
"configuration": {
"itemName": "Yamaha_NetRadio",
"command": "1"
},
"type": "core.ItemCommandAction"
}
]
},
{
"uid": "example_flow_1_3neg_7neg",
"name": "NOT(If the day is SAT,SUN)",
"description": "Created by Flows Builder for the \"else\" path of a condition. Do not run separately, will be overwritten if the flow is published again.",
"visibility": "VISIBLE",
"enabled": true,
"triggers": [],
"conditions": [
{
"id": 1,
"label": "NOT(If the day is SAT,SUN)",
"inputs": {},
"configuration": {
"days": [
"MON",
"TUE",
"WED",
"THU",
"FRI"
]
},
"type": "timer.DayOfWeekCondition"
}
],
"actions": [
{
"id": 2,
"label": "Say \"Welcome home\"",
"inputs": {},
"configuration": {
"text": "Welcome home",
"sink": "webaudio"
},
"type": "media.SayAction"
},
{
"id": 3,
"label": "Run afterwork routine",
"inputs": {},
"configuration": {
"type": "application/javascript",
"script": "print('Afterwork routine')"
},
"type": "script.ScriptAction"
}
]
}
]
There are a number of caveats, which can probably be addressed in the future; most notably, multi-trigger rules, loops and path merges are not possible, for instance this is not allowed:
Anyways, let me know if my design decisions are wrong (some of them are for sure, but it’s only another fun experiment); otherwise, it’s EPL-licensed, so feel free to use it, fork it and do what you want with it
Cheers!