node-red-contrib-dsm
Version:
A dynamic state machine node based on the finite state machine model.
293 lines (240 loc) • 10.5 kB
Markdown
# node-red-contrib-dsm
A dynamic state machine node based on the [Finite state machine](https://en.wikipedia.org/wiki/Finite-state_machine) model.
The `dsm` node is a pure Node.js package for Node Red, it doesn't use any external libraries. The dsm behaviour is defined in a JSON format and can be optianally set/modified programmatically e.g. using an inject node.
#### Principle of operation [Wikipedia]
*A finite-state machine (FSM) is an abstract machine that can be in exactly one of a finite number of states at any given time. The FSM can change from one state to another in response to some external inputs; the change from one state to another is called a transition. A FSM is defined by a list of its states, its initial state, and the conditions for each transition.*
## Usage
The `dsm` properties can be defined in the `edit dsm node` window or by an input JSON object:

#### JSON object:
```
topic: "set"
payload:
{
"triggerInput": "topic",
"stateOutput": "current",
"preStateOutput": "previous",
"globalOutput": "my_global_var",
"flowOutput": "my_flow_var",
"preState": null,
"currentState": "state1",
"states": {
"state1": {
"transition2": "state2"
},
"state2": {
"transition1": "state1",
"transition3": "state3"
},
"state3": {
"transition1": "state1",
"transition2": "state2"
}
}
}
```
`triggerInput`, `stateOutput`, `preStateOutput`, `globalOutput` and `flowOutput` can be omitted.
`triggerInput` and `stateOutput` default `topic`.
`preStateOutput` default `preState`
`globalOutput` and `flowOutput` default `false`.
If `globalOutput` or `flowlOutput` is defined, the corresponding variable is additionally set.
When the node receives a trigger with a transition it will change from one state to another:
```
topic: "transition"
payload: any
```
### Data and methods
In addition to states and transitions a `dsm` can also contain arbitrary data and methods.
#### Example:
```
{
"stateOutput": "payload",
"currentState": "one_state",
"states": {
"recursive": "one_state"
},
"data": {
"delay": 2000
},
"methods": {
"setDelay": "sm.data.delay = msg.payload;",
"getDelay": "msg.delay = sm.data.delay;",
"default": "msg.payload = 'method undefined'"
}
}
```
Methods can be any valid javascript statements. If the method name is the same as the transition name, both the transition and the method are processed.
The method `default` is executed when no method is found in the methods list.
To simplify editing methods can be defined as an array of comma separated strings:
```
"onTransition" : [
"if (sm.data.indoor > sm.data.outdoor) {",
"msg.payload = 'indoor > outdoor';",
"} else if (sm.data.indoor < sm.data.outdoor) {",
"msg.payload = 'indoor < outdoor';",
"} else {",
"msg.payload = 'indoor = outdoor';",
"}"
]
```
### General purpose methods
* `init`
* `onBeforeTransition`
* `onTransition`
* `onAfterTransition`
* `status` defines an arbitrary status
The general purpose methods are executed independently even without states.
The `init` method is executed when the dsm is deployed respectively when the dsm is set. `init` can be used to initialize values.
#### Example:
```
}
"methods": {
"onTransition": "msg.payload = {'sid': global.get('xiaomi_name')[msg.payload]};",
"status": {
"fill": "green",
"shape": "ring",
"text": {
"get": "JSON.stringify(msg.payload)"
}
}
}
}
```
### Build-in methods
To simplify the configuration build-in methods are available.
* setData
* getData
* timer
* resetTimer
* watchdog
### Configuration Examples
#### setData and getData
```
{
"data": {
"temperature": 0,
"humidity": 0,
"pressure": 0,
"co2": 0
},
"methods": {
"temperature": {
"name": "setData"
},
"humidity": {
"name": "setData"
},
"pressure": {
"name": "setData"
},
"co2": {
"name": "setData"
},
"getData": {
"name": "getData"
}
}
}
```
#### timer and watchdog
```
{
"data": {
"delay1": 2000,
"delay2": 3000
},
"methods": {
"timer1": {
"name": "timer",
"param": "delay1"
},
"timer2": {
"name": "timer",
"param": "delay2",
"do": "node.status({});"
},
"timer3": {
"name": "timer",
"param": 5000,
"send": "toilet window timeout"
},
"resetTimer3": {
"name": "resetTimer",
"param": "timer3"
},
"watchdog1": {
"name": "watchdog",
"param": 5000,
"send": {
"get": "msg.payload.name+' timeout';"
}
}
}
}
```
`timer`and `watchdog` param in milliseconds.
`do` executes a statement.
`send` sets msg.payload - either to a string or when using `get` to the result of a statement - and send the msg.
## Examples
### Door

#### Configuration
```
{
"currentState": "closed",
"states": {
"opened": {
"close": "closed",
"stop": "stopped"
},
"closed": {
"open": "opened",
"stop": "stopped"
},
"stopped": {
"open": "opened",
"close": "closed"
}
}
}
```
#### Flow
```
[{"id":"8b02669f.7ddd98","type":"debug","z":"57ef434a.058a6c","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","x":430,"y":1680,"wires":[]},{"id":"68c0e2d5.3b798c","type":"inject","z":"57ef434a.058a6c","name":"open","topic":"open","payload":"your open payload","payloadType":"str","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":90,"y":1640,"wires":[["3b0c292.2da33d6"]]},{"id":"fae17b11.e48a88","type":"inject","z":"57ef434a.058a6c","name":"close","topic":"close","payload":"your close payload","payloadType":"str","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":90,"y":1680,"wires":[["3b0c292.2da33d6"]]},{"id":"3b0c292.2da33d6","type":"dsm","z":"57ef434a.058a6c","name":"door","sm_config":"{\n \"currentState\": \"closed\",\n \"states\": {\n \"opened\": {\n \"close\": \"closed\",\n \"stop\": \"stopped\"\n },\n \"closed\": {\n \"open\": \"opened\",\n \"stop\": \"stopped\"\n },\n \"stopped\": {\n \"open\": \"opened\",\n \"close\": \"closed\"\n }\n }\n}","x":260,"y":1680,"wires":[["8b02669f.7ddd98"]]},{"id":"caac460e.2942c8","type":"inject","z":"57ef434a.058a6c","name":"stop","topic":"stop","payload":"your stop payload","payloadType":"str","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":90,"y":1720,"wires":[["3b0c292.2da33d6"]]}]
```
### Garage door

#### Configuration
```
{
"stateOutput": "payload",
"currentState": "closed",
"states": {
"opened": {
"click": "closing"
},
"closed": {
"click": "opening"
},
"opening": {
"limit_switch": "opened",
"click": "opening_stopped"
},
"closing": {
"limit_switch": "closed",
"click": "closing_stopped"
},
"opening_stopped": {
"click": "closing"
},
"closing_stopped": {
"click": "opening"
}
}
}
```
#### Flow
```
[{"id":"1501e603.fdbeba","type":"dsm","z":"57ef434a.058a6c","name":"garage door","sm_config":"{\n \"stateOutput\": \"payload\",\n \"globalOutput\": \"garage_door_state\",\n \"currentState\": \"closed\",\n \"states\": {\n \"opened\": {\n \"click\": \"closing\"\n },\n \"closed\": {\n \"click\": \"opening\"\n },\n \"opening\": {\n \"limit_switch\": \"opened\",\n \"click\": \"opening_stopped\"\n },\n \"closing\": {\n \"limit_switch\": \"closed\",\n \"click\": \"closing_stopped\"\n },\n \"opening_stopped\": {\n \"click\": \"closing\"\n },\n \"closing_stopped\": {\n \"click\": \"opening\"\n }\n }\n}","x":260,"y":1240,"wires":[["2125cea7.329542"]]},{"id":"4744a8a3.28aec8","type":"inject","z":"57ef434a.058a6c","name":"click","topic":"click","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":90,"y":1240,"wires":[["1501e603.fdbeba"]]},{"id":"379cd28e.34076e","type":"link in","z":"57ef434a.058a6c","name":"to timer","links":["4c183a20.342304"],"x":115,"y":1280,"wires":[["1501e603.fdbeba"]]},{"id":"4c183a20.342304","type":"link out","z":"57ef434a.058a6c","name":"from dsm","links":["379cd28e.34076e"],"x":535,"y":1340,"wires":[]},{"id":"5a467385.1c0bcc","type":"comment","z":"57ef434a.058a6c","name":"motor simulation","info":"","x":440,"y":1200,"wires":[]},{"id":"2125cea7.329542","type":"change","z":"57ef434a.058a6c","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"payload = \"opening\" or payload = \"closing\" ? payload : \"reset\"","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":440,"y":1240,"wires":[["23d5eb3e.3f4fa4"]]},{"id":"23d5eb3e.3f4fa4","type":"trigger","z":"57ef434a.058a6c","op1":"","op2":"limit_switch","op1type":"nul","op2type":"str","duration":"3","extend":false,"units":"s","reset":"reset","bytopic":"topic","name":"","x":320,"y":1340,"wires":[["77a5a197.e301e"]]},{"id":"77a5a197.e301e","type":"change","z":"57ef434a.058a6c","name":"topic","rules":[{"t":"set","p":"topic","pt":"msg","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":450,"y":1340,"wires":[["4c183a20.342304"]]}]
```
For more examples have a look at the [Wiki](https://github.com/cflurin/node-red-contrib-dsm/wiki)