iobroker.statechart 0.0.0-stage → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +559 -2
- package/admin/build/tab.css +2 -0
- package/admin/build/tab.js +921 -0
- package/admin/i18n/de.json +4 -0
- package/admin/i18n/en.json +4 -0
- package/admin/i18n/es.json +4 -0
- package/admin/i18n/fr.json +4 -0
- package/admin/i18n/it.json +4 -0
- package/admin/i18n/nl.json +4 -0
- package/admin/i18n/pl.json +4 -0
- package/admin/i18n/pt.json +4 -0
- package/admin/i18n/ru.json +4 -0
- package/admin/i18n/uk.json +4 -0
- package/admin/i18n/zh-cn.json +4 -0
- package/admin/jsonConfig.json +19 -0
- package/admin/statechart.png +0 -0
- package/admin/statechart.svg +38 -0
- package/admin/style.css +14 -0
- package/admin/tab_m.html +34 -0
- package/io-package.json +126 -0
- package/lib/adapter-config.d.ts +19 -0
- package/lib/chart-codegen.js +436 -0
- package/lib/chart-codegen.test.js +132 -0
- package/lib/chart-helpers.js +74 -0
- package/lib/chart-helpers.test.js +38 -0
- package/lib/chart-model.js +577 -0
- package/lib/chart-model.test.js +189 -0
- package/lib/chart-runtime.js +797 -0
- package/lib/chart-runtime.test.js +699 -0
- package/lib/chart-structure.js +106 -0
- package/lib/chart-validation.js +328 -0
- package/lib/chart-validation.test.js +149 -0
- package/main.js +279 -0
- package/package.json +100 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 IggePop
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,560 @@
|
|
|
1
|
-
|
|
1
|
+

|
|
2
|
+
# ioBroker.statechart
|
|
2
3
|
|
|
3
|
-
|
|
4
|
+
[](https://www.npmjs.com/package/iobroker.statechart)
|
|
5
|
+
[](https://www.npmjs.com/package/iobroker.statechart)
|
|
6
|
+

|
|
7
|
+

|
|
8
|
+
|
|
9
|
+
[](https://nodei.co/npm/iobroker.statechart/)
|
|
10
|
+
|
|
11
|
+
**Tests:** 
|
|
12
|
+
|
|
13
|
+
**[English](#english) | [Deutsch](#deutsch)**
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
<a id="english"></a>
|
|
18
|
+
|
|
19
|
+
## State machine adapter for ioBroker
|
|
20
|
+
|
|
21
|
+
Model your automations as **state machines** (statecharts) in a graphical editor, similar to the
|
|
22
|
+
chart editors known from model based development. States, transitions with events, conditions and
|
|
23
|
+
actions, timers and function blocks are drawn in the ioBroker admin and executed by the adapter.
|
|
24
|
+
|
|
25
|
+
### Features
|
|
26
|
+
|
|
27
|
+
- Graphical editor as its own entry in the admin side bar
|
|
28
|
+
- States with `entry`, `during` and `exit` actions
|
|
29
|
+
- Transitions with labels in the well-known syntax `event[condition]{action}`
|
|
30
|
+
- Timers: `after(5, sec)`
|
|
31
|
+
- Function blocks with your own JavaScript functions, usable in all conditions and actions
|
|
32
|
+
- Inputs and outputs mapped to any ioBroker states
|
|
33
|
+
- Live view: the active state is highlighted in the editor
|
|
34
|
+
- Code editor with syntax highlighting, error checking (e.g. unknown variables) and completion
|
|
35
|
+
- Several independent charts per instance
|
|
36
|
+
- Import and export of charts as JSON files
|
|
37
|
+
- Comments on states, transitions and function blocks, and notes on the canvas
|
|
38
|
+
- Per input: wake up on every update or only on changes, filter by ack, debounce
|
|
39
|
+
- Helper functions: setState, setStateDelayed, sendTo, isTimeBetween, isDaytime, astroTime
|
|
40
|
+
- Undo/redo (Ctrl+Z, Ctrl+Y), copy/cut/paste of states and function blocks (Ctrl+C, Ctrl+X, Ctrl+V), also between charts, and a warning about unsaved changes
|
|
41
|
+
- **Show code**: the JavaScript generated from the chart, showing exactly how the adapter executes it
|
|
42
|
+
|
|
43
|
+
### Installation
|
|
44
|
+
|
|
45
|
+
The adapter is not in the ioBroker repositories yet, but can be installed directly from GitHub:
|
|
46
|
+
|
|
47
|
+
- **Admin:** tab **Adapters** → icon **Install from custom URL** (GitHub cat) → tab **Custom** →
|
|
48
|
+
URL `https://github.com/IggePop/ioBroker.statechart` → **Install**
|
|
49
|
+
- **or on the console** of the ioBroker system:
|
|
50
|
+
```bash
|
|
51
|
+
iobroker url https://github.com/IggePop/ioBroker.statechart
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Then add an instance of **State Machine**. This installs the current development version, which may
|
|
55
|
+
still change incompatibly.
|
|
56
|
+
|
|
57
|
+
Requirements: Node.js 22 or newer, js-controller 6.0.11 or newer, admin 7.6.17 or newer.
|
|
58
|
+
|
|
59
|
+
### Getting started
|
|
60
|
+
|
|
61
|
+
1. Open **State Machine** in the admin side bar.
|
|
62
|
+
2. Create a chart with **+** in the list on the left.
|
|
63
|
+
3. Define the variables in the tab **Variables** on the right:
|
|
64
|
+
- **Input**: read from an ioBroker state, every change wakes up the chart
|
|
65
|
+
- **Output**: written to an ioBroker state when an action changes it
|
|
66
|
+
- **Local**: only exists inside the chart, with an initial value
|
|
67
|
+
4. Add states with **Add state**. The state marked with the dot and arrow is the initial state
|
|
68
|
+
(can be changed in the properties of a state).
|
|
69
|
+
5. Draw transitions by dragging from the border of a state to another state (or to itself).
|
|
70
|
+
6. Select a state or transition (or double click it) to edit its actions or label on the right.
|
|
71
|
+
7. Click **Save**. New charts are paused: start the chart with ▶ in the chart list. After saving, the adapter
|
|
72
|
+
restarts a running chart immediately.
|
|
73
|
+
|
|
74
|
+
### Example: light with motion detector
|
|
75
|
+
|
|
76
|
+
| Variable | Scope | State |
|
|
77
|
+
|---|---|---|
|
|
78
|
+
| `motion` | Input | e.g. `hm-rpc.0.ABC123.1.MOTION` |
|
|
79
|
+
| `light` | Output | e.g. `hue.0.hallway.on` |
|
|
80
|
+
|
|
81
|
+
| State | entry |
|
|
82
|
+
|---|---|
|
|
83
|
+
| Off (initial state) | `light = false;` |
|
|
84
|
+
| On | `light = true;` |
|
|
85
|
+
|
|
86
|
+
| Transition | Label |
|
|
87
|
+
|---|---|
|
|
88
|
+
| Off → On | `motion[motion == true]` |
|
|
89
|
+
| On → Off | `after(2, min)[!motion]` |
|
|
90
|
+
|
|
91
|
+
### Transition labels
|
|
92
|
+
|
|
93
|
+
```
|
|
94
|
+
event[condition]{action}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
All parts are optional and may span several lines.
|
|
98
|
+
|
|
99
|
+
| Part | Meaning | Example |
|
|
100
|
+
|---|---|---|
|
|
101
|
+
| `event` | Name of an input: the transition is only checked when this input changes | `motion` |
|
|
102
|
+
| `after(n, unit)` | Instead of an event: valid from *n* units after entering the state on. Then a timer wakes up the chart, and the condition is checked again on every following wakeup. Units: `msec`, `sec`, `min` | `after(30, sec)` |
|
|
103
|
+
| `[condition]` | JavaScript expression, the transition is only taken if it is true | `[temp > 21 && !window]` |
|
|
104
|
+
| `{action}` | JavaScript, executed when the transition is taken | `{count++; light = true;}` |
|
|
105
|
+
|
|
106
|
+
Without event, a transition is checked whenever any input changes. If a state has several outgoing
|
|
107
|
+
transitions, they are checked in the order of their **priority** (shown at the arrow).
|
|
108
|
+
|
|
109
|
+
### When inputs wake up the chart
|
|
110
|
+
|
|
111
|
+
Every input has three settings in the tab **Variables**:
|
|
112
|
+
|
|
113
|
+
| Setting | Values | Default |
|
|
114
|
+
|---|---|---|
|
|
115
|
+
| **Wake up on** | every update / value changes only | every update |
|
|
116
|
+
| **Acknowledged (ack)** | any / confirmed values only (`ack=true`) / commands only (`ack=false`) | any |
|
|
117
|
+
| **Debounce (ms)** | the chart wakes up only after the input had no update for this time, with its last value | 0 (off) |
|
|
118
|
+
|
|
119
|
+
Updates filtered out by ack do not change the variable, so it e.g. only contains confirmed values.
|
|
120
|
+
|
|
121
|
+
### Actions
|
|
122
|
+
|
|
123
|
+
Actions and conditions are JavaScript. All variables of the chart can be used directly by their name,
|
|
124
|
+
plus `Math`, `Date`, `JSON` etc. and `log("text")` to write to the ioBroker log.
|
|
125
|
+
|
|
126
|
+
| Action | Executed |
|
|
127
|
+
|---|---|
|
|
128
|
+
| `entry` | when the state is entered |
|
|
129
|
+
| `during` | on every wakeup of the chart while the state is active and none of its outgoing transitions is taken |
|
|
130
|
+
| `exit` | when the state is left |
|
|
131
|
+
|
|
132
|
+
### Nested states
|
|
133
|
+
|
|
134
|
+
Drop a state into another state to make it a **sub state**, drag it out again to undo it. While dragging,
|
|
135
|
+
the state it would be moved into is highlighted. Every level has its own initial state (dot with arrow).
|
|
136
|
+
|
|
137
|
+
- Entering a super state also enters its initial sub state (or the sub state the transition points to).
|
|
138
|
+
Leaving it leaves the active sub state first.
|
|
139
|
+
- On every wakeup, each active state is processed in this order, starting with the outermost one:
|
|
140
|
+
1. transitions leaving the state, 2. its `during` action, 3. its **inner transitions**, 4. its active sub state.
|
|
141
|
+
So the transitions of a super state have priority over those of its sub states.
|
|
142
|
+
- A transition from a state to one of its own sub states is an **inner transition** (dot at its start):
|
|
143
|
+
the state stays active, only its active sub state is left, its own `entry`/`exit` are not executed.
|
|
144
|
+
With the option **Leave and enter the parent state again**, it is left and entered again instead.
|
|
145
|
+
|
|
146
|
+
### Helper functions
|
|
147
|
+
|
|
148
|
+
In conditions, actions and function blocks:
|
|
149
|
+
|
|
150
|
+
| Function | Purpose |
|
|
151
|
+
|---|---|
|
|
152
|
+
| `log(message, level)` | writes to the ioBroker log, level `"debug"`, `"info"` (default), `"warn"` or `"error"` |
|
|
153
|
+
| `setState(id, value, ack)` | writes any ioBroker state, `ack` is `false` by default |
|
|
154
|
+
| `setStateDelayed(id, value, delayMs, ack)` | writes after a delay, cancelled when the chart stops |
|
|
155
|
+
| `sendTo(instance, command, message)` | sends a message to an adapter, e.g. `sendTo("telegram.0", "send", { text: "Window open" })` |
|
|
156
|
+
| `isTimeBetween("22:00", "06:00")` | true if the current time is in the range, also over midnight |
|
|
157
|
+
| `isDaytime()` | true between sunrise and sunset |
|
|
158
|
+
| `astroTime("sunset", offsetMinutes)` | time of an astro event today as `Date`: `sunrise`, `sunset`, `dawn`, `dusk`, `solarNoon`, ... |
|
|
159
|
+
|
|
160
|
+
`isDaytime()` and `astroTime()` use the location of the ioBroker system settings. To read other states, add
|
|
161
|
+
them as inputs: their variables are always up to date and wake up the chart. Time based functions do not wake up
|
|
162
|
+
the chart by themselves, they are evaluated when it wakes up.
|
|
163
|
+
|
|
164
|
+
### Function blocks
|
|
165
|
+
|
|
166
|
+
**Add function** creates a block for your own JavaScript. It is executed once when the chart starts,
|
|
167
|
+
all functions and constants declared in it can be used in every condition and action of the chart:
|
|
168
|
+
|
|
169
|
+
```js
|
|
170
|
+
const LIMIT = 22;
|
|
171
|
+
|
|
172
|
+
function tooWarm(temp) {
|
|
173
|
+
return temp > LIMIT;
|
|
174
|
+
}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Transition label: `[tooWarm(temperature)]{heating = false;}`
|
|
178
|
+
|
|
179
|
+
### Comments and notes
|
|
180
|
+
|
|
181
|
+
- **Comment:** states, transitions, function blocks and the chart itself have a field **Comment** in their properties.
|
|
182
|
+
A speech bubble on the canvas shows that there is a comment, the comment is shown as tooltip.
|
|
183
|
+
- **Notes** (icon **Add note** in the toolbar) are free texts on the canvas, like sticky notes. Drop a note into a
|
|
184
|
+
state, so it moves together with it.
|
|
185
|
+
|
|
186
|
+
Comments and notes have no influence on the execution. They are included in the generated code and in exported
|
|
187
|
+
files, and changing them does not prevent restoring the state after a restart.
|
|
188
|
+
|
|
189
|
+
### Import and export
|
|
190
|
+
|
|
191
|
+
- **Export chart** (last icon in the toolbar of the editor) saves the chart as JSON file, including unsaved changes.
|
|
192
|
+
- **Export all charts** (icon above the chart list) saves all saved charts in one file.
|
|
193
|
+
- **Import charts** (icon above the chart list) adds the charts of such a file as new charts, existing charts are not
|
|
194
|
+
changed. The file is checked before (structure, IDs, references between states and transitions).
|
|
195
|
+
|
|
196
|
+
Imported charts are **paused**: their variables may refer to states of another system. Check them in the tab
|
|
197
|
+
**Variables**, then start the chart with the button in the chart list.
|
|
198
|
+
|
|
199
|
+
### States created by the adapter
|
|
200
|
+
|
|
201
|
+
For every chart, the adapter creates below `statechart.<instance>.charts.<chart>`:
|
|
202
|
+
|
|
203
|
+
| State | Content |
|
|
204
|
+
|---|---|
|
|
205
|
+
| `activeState` | Name of the active state, nested states as `Parent.Child` |
|
|
206
|
+
| `activeStateIds` | IDs of all active states as JSON |
|
|
207
|
+
| `snapshot` | Stored state of the chart (active states, when they were entered, locals), see below |
|
|
208
|
+
|
|
209
|
+
The chart itself is stored in the object `statechart.<instance>.charts.<chart>`.
|
|
210
|
+
|
|
211
|
+
### Restart of the adapter
|
|
212
|
+
|
|
213
|
+
After a restart of the adapter or of ioBroker, every chart continues in the states which were active
|
|
214
|
+
before, with its local variables. The entry actions are not executed again, and timers continue with
|
|
215
|
+
their remaining time. A timer which expired while the adapter was stopped is taken right after the start.
|
|
216
|
+
|
|
217
|
+
The chart starts in its initial state instead if it was changed meanwhile (positions in the editor do not
|
|
218
|
+
count), after saving it in the editor, after pausing it, and if the option **Restore the state after a
|
|
219
|
+
restart** in the chart properties is switched off. Variables declared in function blocks are not restored.
|
|
220
|
+
|
|
221
|
+
### Execution model and differences to other statechart tools
|
|
222
|
+
|
|
223
|
+
- The adapter uses [XState](https://stately.ai/docs/xstate) to execute the charts.
|
|
224
|
+
- Every change of an input wakes up the chart once. Transitions without event are checked on every wakeup.
|
|
225
|
+
- `{action}` is executed while the transition is taken, i.e. after `exit` of the old and before `entry` of
|
|
226
|
+
the new state. A separate condition action and `/action` are not supported (yet).
|
|
227
|
+
- Transitions are selected like in Stateflow (outer before inner, see above), XState only executes them.
|
|
228
|
+
Pure XState would check the transitions of sub states first.
|
|
229
|
+
- Temporal logic is limited to `after(n, msec|sec|min)`, measured in real time.
|
|
230
|
+
- Conditions and actions use JavaScript, not MATLAB/C syntax.
|
|
231
|
+
|
|
232
|
+
### Limitations
|
|
233
|
+
|
|
234
|
+
- Parallel states (AND), history junctions and connective junctions are not supported yet.
|
|
235
|
+
- Deleting a super state also deletes its sub states.
|
|
236
|
+
- Unsaved changes are lost when you select another chart.
|
|
237
|
+
- Conditions and actions run in a separate JavaScript context with a time limit of 1 second, but this
|
|
238
|
+
is **no security sandbox**: everyone who may edit charts can execute any code in the adapter,
|
|
239
|
+
just like with the JavaScript adapter.
|
|
240
|
+
|
|
241
|
+
---
|
|
242
|
+
|
|
243
|
+
<a id="deutsch"></a>
|
|
244
|
+
|
|
245
|
+
## Zustandsautomaten-Adapter für ioBroker
|
|
246
|
+
|
|
247
|
+
Bilde deine Automatisierungen als **Zustandsautomaten** (Statecharts) in einem grafischen Editor ab,
|
|
248
|
+
ähnlich den Chart-Editoren aus der modellbasierten Entwicklung. Zustände, Übergänge mit Events,
|
|
249
|
+
Bedingungen und Aktionen, Timer und Funktionsblöcke werden im ioBroker-Admin gezeichnet und vom
|
|
250
|
+
Adapter ausgeführt.
|
|
251
|
+
|
|
252
|
+
### Funktionen
|
|
253
|
+
|
|
254
|
+
- Grafischer Editor als eigener Eintrag in der Seitenleiste des Admins
|
|
255
|
+
- Zustände mit `entry`-, `during`- und `exit`-Aktionen
|
|
256
|
+
- Übergänge mit Beschriftungen in der bekannten Syntax `event[bedingung]{aktion}`
|
|
257
|
+
- Timer: `after(5, sec)`
|
|
258
|
+
- Funktionsblöcke mit eigenen JavaScript-Funktionen, nutzbar in allen Bedingungen und Aktionen
|
|
259
|
+
- Eingänge und Ausgänge, die mit beliebigen ioBroker-Datenpunkten verbunden sind
|
|
260
|
+
- Live-Ansicht: Der aktive Zustand wird im Editor hervorgehoben
|
|
261
|
+
- Code-Editor mit Syntax-Hervorhebung, Fehlerprüfung (z.B. unbekannte Variablen) und Autovervollständigung
|
|
262
|
+
- Mehrere unabhängige Charts pro Instanz
|
|
263
|
+
- Import und Export von Charts als JSON-Dateien
|
|
264
|
+
- Kommentare zu Zuständen, Übergängen und Funktionsblöcken sowie Notizen auf der Zeichenfläche
|
|
265
|
+
- Pro Eingang: Aufwecken bei jeder Aktualisierung oder nur bei Änderung, Filter nach ack, Entprellen
|
|
266
|
+
- Hilfsfunktionen: setState, setStateDelayed, sendTo, isTimeBetween, isDaytime, astroTime
|
|
267
|
+
- Rückgängig/Wiederholen (Strg+Z, Strg+Y), Kopieren/Ausschneiden/Einfügen von Zuständen und Funktionsblöcken (Strg+C, Strg+X, Strg+V), auch zwischen Charts, und eine Warnung bei ungespeicherten Änderungen
|
|
268
|
+
- **Code anzeigen**: das aus dem Chart generierte JavaScript, das genau zeigt, wie der Adapter es ausführt
|
|
269
|
+
|
|
270
|
+
### Installation
|
|
271
|
+
|
|
272
|
+
Der Adapter ist noch nicht in den ioBroker-Repositories, lässt sich aber direkt von GitHub installieren:
|
|
273
|
+
|
|
274
|
+
- **Admin:** Reiter **Adapter** → Symbol **Aus eigener URL installieren** (GitHub-Katze) → Reiter
|
|
275
|
+
**Benutzerdefiniert** → URL `https://github.com/IggePop/ioBroker.statechart` → **Installieren**
|
|
276
|
+
- **oder auf der Konsole** des ioBroker-Systems:
|
|
277
|
+
```bash
|
|
278
|
+
iobroker url https://github.com/IggePop/ioBroker.statechart
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
Danach eine Instanz von **Zustandsmaschine** hinzufügen. Installiert wird der aktuelle
|
|
282
|
+
Entwicklungsstand, der sich noch inkompatibel ändern kann.
|
|
283
|
+
|
|
284
|
+
Voraussetzungen: Node.js 22 oder neuer, js-controller 6.0.11 oder neuer, Admin 7.6.17 oder neuer.
|
|
285
|
+
|
|
286
|
+
### Erste Schritte
|
|
287
|
+
|
|
288
|
+
1. In der Seitenleiste des Admins **Zustandsmaschine** öffnen.
|
|
289
|
+
2. Links in der Liste mit **+** ein Chart anlegen.
|
|
290
|
+
3. Rechts im Reiter **Variablen** die Variablen festlegen:
|
|
291
|
+
- **Eingang**: wird aus einem ioBroker-Datenpunkt gelesen, jede Änderung weckt das Chart auf
|
|
292
|
+
- **Ausgang**: wird in einen ioBroker-Datenpunkt geschrieben, wenn eine Aktion ihn ändert
|
|
293
|
+
- **Lokal**: gibt es nur im Chart, mit einem Startwert
|
|
294
|
+
4. Mit **Zustand hinzufügen** Zustände anlegen. Der Zustand mit Punkt und Pfeil ist der Startzustand
|
|
295
|
+
(änderbar in den Eigenschaften eines Zustands).
|
|
296
|
+
5. Übergänge zeichnen: vom Rand eines Zustands zu einem anderen Zustand (oder zu sich selbst) ziehen.
|
|
297
|
+
6. Einen Zustand oder Übergang anklicken (oder doppelklicken), um rechts seine Aktionen bzw. seine
|
|
298
|
+
Beschriftung zu bearbeiten.
|
|
299
|
+
7. **Speichern** klicken. Neue Charts sind angehalten: Starte das Chart mit ▶ in der Chart-Liste. Nach dem
|
|
300
|
+
Speichern startet der Adapter ein laufendes Chart sofort neu.
|
|
301
|
+
|
|
302
|
+
### Beispiel: Licht mit Bewegungsmelder
|
|
303
|
+
|
|
304
|
+
| Variable | Art | Datenpunkt |
|
|
305
|
+
|---|---|---|
|
|
306
|
+
| `motion` | Eingang | z.B. `hm-rpc.0.ABC123.1.MOTION` |
|
|
307
|
+
| `light` | Ausgang | z.B. `hue.0.flur.on` |
|
|
308
|
+
|
|
309
|
+
| Zustand | entry |
|
|
310
|
+
|---|---|
|
|
311
|
+
| Aus (Startzustand) | `light = false;` |
|
|
312
|
+
| An | `light = true;` |
|
|
313
|
+
|
|
314
|
+
| Übergang | Beschriftung |
|
|
315
|
+
|---|---|
|
|
316
|
+
| Aus → An | `motion[motion == true]` |
|
|
317
|
+
| An → Aus | `after(2, min)[!motion]` |
|
|
318
|
+
|
|
319
|
+
### Beschriftung von Übergängen
|
|
320
|
+
|
|
321
|
+
```
|
|
322
|
+
event[bedingung]{aktion}
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
Alle Teile sind optional und dürfen über mehrere Zeilen gehen.
|
|
326
|
+
|
|
327
|
+
| Teil | Bedeutung | Beispiel |
|
|
328
|
+
|---|---|---|
|
|
329
|
+
| `event` | Name eines Eingangs: Der Übergang wird nur geprüft, wenn sich dieser Eingang ändert | `motion` |
|
|
330
|
+
| `after(n, einheit)` | Statt eines Events: gültig ab *n* Einheiten nach Betreten des Zustands. Dann weckt ein Timer das Chart, und die Bedingung wird bei jedem weiteren Aufwecken erneut geprüft. Einheiten: `msec`, `sec`, `min` | `after(30, sec)` |
|
|
331
|
+
| `[bedingung]` | JavaScript-Ausdruck, der Übergang wird nur genommen, wenn er wahr ist | `[temp > 21 && !fenster]` |
|
|
332
|
+
| `{aktion}` | JavaScript, wird ausgeführt, wenn der Übergang genommen wird | `{zaehler++; licht = true;}` |
|
|
333
|
+
|
|
334
|
+
Ohne Event wird ein Übergang bei jeder Änderung eines beliebigen Eingangs geprüft. Hat ein Zustand
|
|
335
|
+
mehrere ausgehende Übergänge, werden sie in der Reihenfolge ihrer **Priorität** geprüft (steht am Pfeil).
|
|
336
|
+
|
|
337
|
+
### Wann Eingänge das Chart aufwecken
|
|
338
|
+
|
|
339
|
+
Jeder Eingang hat im Reiter **Variablen** drei Einstellungen:
|
|
340
|
+
|
|
341
|
+
| Einstellung | Werte | Standard |
|
|
342
|
+
|---|---|---|
|
|
343
|
+
| **Aufwecken bei** | jeder Aktualisierung / nur bei Wertänderung | jeder Aktualisierung |
|
|
344
|
+
| **Bestätigt (ack)** | egal / nur bestätigte Werte (`ack=true`) / nur Befehle (`ack=false`) | egal |
|
|
345
|
+
| **Entprellen (ms)** | das Chart wird erst geweckt, wenn der Eingang so lange keine Aktualisierung bekommen hat, mit dem letzten Wert | 0 (aus) |
|
|
346
|
+
|
|
347
|
+
Aktualisierungen, die wegen ack aussortiert werden, ändern die Variable nicht. Sie enthält dann z.B. nur bestätigte Werte.
|
|
348
|
+
|
|
349
|
+
### Aktionen
|
|
350
|
+
|
|
351
|
+
Aktionen und Bedingungen sind JavaScript. Alle Variablen des Charts sind direkt über ihren Namen
|
|
352
|
+
verfügbar, dazu `Math`, `Date`, `JSON` usw. sowie `log("Text")` zum Schreiben ins ioBroker-Log.
|
|
353
|
+
|
|
354
|
+
| Aktion | Wird ausgeführt |
|
|
355
|
+
|---|---|
|
|
356
|
+
| `entry` | beim Betreten des Zustands |
|
|
357
|
+
| `during` | bei jedem Aufwecken des Charts, solange der Zustand aktiv ist und keiner seiner ausgehenden Übergänge genommen wird |
|
|
358
|
+
| `exit` | beim Verlassen des Zustands |
|
|
359
|
+
|
|
360
|
+
### Verschachtelte Zustände
|
|
361
|
+
|
|
362
|
+
Lass einen Zustand über einem anderen los, um ihn zum **Unterzustand** zu machen, und zieh ihn wieder
|
|
363
|
+
heraus, um das rückgängig zu machen. Beim Ziehen wird der Zustand hervorgehoben, in den er verschoben
|
|
364
|
+
würde. Jede Ebene hat ihren eigenen Startzustand (Punkt mit Pfeil).
|
|
365
|
+
|
|
366
|
+
- Beim Betreten eines Superstates wird auch sein Start-Unterzustand betreten (oder der Unterzustand,
|
|
367
|
+
auf den der Übergang zeigt). Beim Verlassen wird zuerst der aktive Unterzustand verlassen.
|
|
368
|
+
- Bei jedem Aufwecken wird jeder aktive Zustand in dieser Reihenfolge bearbeitet, beginnend mit dem äußersten:
|
|
369
|
+
1. Übergänge, die den Zustand verlassen, 2. seine `during`-Aktion, 3. seine **inneren Übergänge**,
|
|
370
|
+
4. sein aktiver Unterzustand. Die Übergänge eines Superstates haben also Vorrang vor denen seiner Unterzustände.
|
|
371
|
+
- Ein Übergang von einem Zustand zu einem seiner eigenen Unterzustände ist ein **innerer Übergang**
|
|
372
|
+
(Punkt am Anfang): Der Zustand bleibt aktiv, nur sein aktiver Unterzustand wird verlassen, seine
|
|
373
|
+
eigenen `entry`/`exit` laufen nicht. Mit der Option **Elternzustand verlassen und neu betreten** wird
|
|
374
|
+
er stattdessen verlassen und neu betreten.
|
|
375
|
+
|
|
376
|
+
### Hilfsfunktionen
|
|
377
|
+
|
|
378
|
+
In Bedingungen, Aktionen und Funktionsblöcken:
|
|
379
|
+
|
|
380
|
+
| Funktion | Zweck |
|
|
381
|
+
|---|---|
|
|
382
|
+
| `log(text, stufe)` | schreibt ins ioBroker-Log, Stufe `"debug"`, `"info"` (Standard), `"warn"` oder `"error"` |
|
|
383
|
+
| `setState(id, wert, ack)` | schreibt einen beliebigen Datenpunkt, `ack` ist standardmäßig `false` |
|
|
384
|
+
| `setStateDelayed(id, wert, ms, ack)` | schreibt verzögert, wird beim Anhalten des Charts verworfen |
|
|
385
|
+
| `sendTo(instanz, befehl, nachricht)` | sendet eine Nachricht an einen Adapter, z.B. `sendTo("telegram.0", "send", { text: "Fenster offen" })` |
|
|
386
|
+
| `isTimeBetween("22:00", "06:00")` | wahr, wenn die aktuelle Uhrzeit im Bereich liegt, auch über Mitternacht |
|
|
387
|
+
| `isDaytime()` | wahr zwischen Sonnenaufgang und Sonnenuntergang |
|
|
388
|
+
| `astroTime("sunset", minuten)` | Zeitpunkt eines Astro-Ereignisses heute als `Date`: `sunrise`, `sunset`, `dawn`, `dusk`, `solarNoon`, ... |
|
|
389
|
+
|
|
390
|
+
`isDaytime()` und `astroTime()` nutzen den Standort aus den ioBroker-Systemeinstellungen. Andere Datenpunkte liest
|
|
391
|
+
man über Eingänge: Ihre Variablen sind immer aktuell und wecken das Chart. Zeitabhängige Funktionen wecken das
|
|
392
|
+
Chart nicht von selbst, sie werden beim Aufwecken ausgewertet.
|
|
393
|
+
|
|
394
|
+
### Funktionsblöcke
|
|
395
|
+
|
|
396
|
+
**Funktion hinzufügen** legt einen Block für eigenes JavaScript an. Er wird beim Start des Charts
|
|
397
|
+
einmal ausgeführt. Alle darin deklarierten Funktionen und Konstanten können in jeder Bedingung und
|
|
398
|
+
Aktion des Charts verwendet werden:
|
|
399
|
+
|
|
400
|
+
```js
|
|
401
|
+
const LIMIT = 22;
|
|
402
|
+
|
|
403
|
+
function zuWarm(temp) {
|
|
404
|
+
return temp > LIMIT;
|
|
405
|
+
}
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
Beschriftung eines Übergangs: `[zuWarm(temperatur)]{heizung = false;}`
|
|
409
|
+
|
|
410
|
+
### Kommentare und Notizen
|
|
411
|
+
|
|
412
|
+
- **Kommentar:** Zustände, Übergänge, Funktionsblöcke und das Chart selbst haben in ihren Eigenschaften ein Feld
|
|
413
|
+
**Kommentar**. Eine Sprechblase auf der Zeichenfläche zeigt, dass es einen Kommentar gibt, der Kommentar erscheint
|
|
414
|
+
als Tooltip.
|
|
415
|
+
- **Notizen** (Symbol **Notiz hinzufügen** in der Werkzeugleiste) sind freie Texte auf der Zeichenfläche, wie
|
|
416
|
+
Haftzettel. Lass eine Notiz über einem Zustand los, dann bewegt sie sich mit ihm.
|
|
417
|
+
|
|
418
|
+
Kommentare und Notizen haben keinen Einfluss auf die Ausführung. Sie stehen im generierten Code und in exportierten
|
|
419
|
+
Dateien, und wenn sie sich ändern, wird der Zustand nach einem Neustart trotzdem wiederhergestellt.
|
|
420
|
+
|
|
421
|
+
### Import und Export
|
|
422
|
+
|
|
423
|
+
- **Chart exportieren** (letztes Symbol in der Werkzeugleiste des Editors) speichert das Chart als JSON-Datei, mit
|
|
424
|
+
ungespeicherten Änderungen.
|
|
425
|
+
- **Alle Charts exportieren** (Symbol über der Chart-Liste) speichert alle gespeicherten Charts in einer Datei.
|
|
426
|
+
- **Charts importieren** (Symbol über der Chart-Liste) fügt die Charts einer solchen Datei als neue Charts hinzu,
|
|
427
|
+
vorhandene Charts werden nicht verändert. Die Datei wird vorher geprüft (Aufbau, IDs, Verweise zwischen Zuständen
|
|
428
|
+
und Übergängen).
|
|
429
|
+
|
|
430
|
+
Importierte Charts sind **angehalten**: Ihre Variablen können auf Datenpunkte eines anderen Systems verweisen. Prüfe sie
|
|
431
|
+
im Reiter **Variablen** und starte das Chart dann mit dem Knopf in der Chart-Liste.
|
|
432
|
+
|
|
433
|
+
### Vom Adapter angelegte Datenpunkte
|
|
434
|
+
|
|
435
|
+
Für jedes Chart legt der Adapter unter `statechart.<instanz>.charts.<chart>` an:
|
|
436
|
+
|
|
437
|
+
| Datenpunkt | Inhalt |
|
|
438
|
+
|---|---|
|
|
439
|
+
| `activeState` | Name des aktiven Zustands, verschachtelte Zustände als `Eltern.Kind` |
|
|
440
|
+
| `activeStateIds` | IDs aller aktiven Zustände als JSON |
|
|
441
|
+
| `snapshot` | Gespeicherter Zustand des Charts (aktive Zustände, wann sie betreten wurden, lokale Variablen), siehe unten |
|
|
442
|
+
|
|
443
|
+
Das Chart selbst ist im Objekt `statechart.<instanz>.charts.<chart>` gespeichert.
|
|
444
|
+
|
|
445
|
+
### Neustart des Adapters
|
|
446
|
+
|
|
447
|
+
Nach einem Neustart des Adapters oder von ioBroker macht jedes Chart in den Zuständen weiter, die
|
|
448
|
+
vorher aktiv waren, mit seinen lokalen Variablen. Die entry-Aktionen laufen nicht noch einmal, und Timer
|
|
449
|
+
laufen mit ihrer Restzeit weiter. Ein Timer, der abgelaufen ist, während der Adapter gestoppt war, greift
|
|
450
|
+
gleich nach dem Start.
|
|
451
|
+
|
|
452
|
+
Im Startzustand beginnt das Chart stattdessen, wenn es zwischendurch geändert wurde (Positionen im
|
|
453
|
+
Editor zählen nicht), nach dem Speichern im Editor, nach dem Anhalten und wenn die Option **Zustand nach
|
|
454
|
+
Neustart wiederherstellen** in den Chart-Eigenschaften ausgeschaltet ist. Variablen, die in
|
|
455
|
+
Funktionsblöcken deklariert sind, werden nicht wiederhergestellt.
|
|
456
|
+
|
|
457
|
+
### Ausführung und Unterschiede zu anderen Statechart-Werkzeugen
|
|
458
|
+
|
|
459
|
+
- Der Adapter führt die Charts mit [XState](https://stately.ai/docs/xstate) aus.
|
|
460
|
+
- Jede Änderung eines Eingangs weckt das Chart einmal auf. Übergänge ohne Event werden bei jedem Aufwecken geprüft.
|
|
461
|
+
- `{aktion}` wird während des Übergangs ausgeführt, also nach `exit` des alten und vor `entry` des
|
|
462
|
+
neuen Zustands. Eine eigene Condition Action und `/aktion` werden (noch) nicht unterstützt.
|
|
463
|
+
- Übergänge werden wie bei Stateflow ausgewählt (außen vor innen, siehe oben), XState führt sie nur aus.
|
|
464
|
+
Reines XState würde die Übergänge der Unterzustände zuerst prüfen.
|
|
465
|
+
- Zeitlogik gibt es nur als `after(n, msec|sec|min)`, gemessen in Echtzeit.
|
|
466
|
+
- Bedingungen und Aktionen sind JavaScript, nicht MATLAB- oder C-Syntax.
|
|
467
|
+
|
|
468
|
+
### Einschränkungen
|
|
469
|
+
|
|
470
|
+
- Parallele Zustände (AND), History- und Verbindungspunkte werden noch nicht unterstützt.
|
|
471
|
+
- Beim Löschen eines Superstates werden auch seine Unterzustände gelöscht.
|
|
472
|
+
- Ungespeicherte Änderungen gehen verloren, wenn ein anderes Chart ausgewählt wird.
|
|
473
|
+
- Bedingungen und Aktionen laufen in einem eigenen JavaScript-Kontext mit 1 Sekunde Zeitlimit, das
|
|
474
|
+
ist aber **keine Sicherheits-Sandbox**: Wer Charts bearbeiten darf, kann im Adapter beliebigen Code
|
|
475
|
+
ausführen, wie beim JavaScript-Adapter auch.
|
|
476
|
+
|
|
477
|
+
---
|
|
478
|
+
|
|
479
|
+
## Development / Entwicklung
|
|
480
|
+
|
|
481
|
+
```bash
|
|
482
|
+
npm install
|
|
483
|
+
npm run build # build the admin tab
|
|
484
|
+
npm run dev-server setup # once: local ioBroker test instance in .dev-server/
|
|
485
|
+
npm run dev-server watch # run it, admin on http://127.0.0.1:8081/
|
|
486
|
+
npm run upload:tab # in a second terminal: upload the tab into the test instance
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
| Script | Description |
|
|
490
|
+
|---|---|
|
|
491
|
+
| `dev-server` | Runs [`dev-server`](https://github.com/ioBroker/dev-server) with the given command. Before, it applies the patches in `patches/`: the dev-server uses `wmic.exe` to find the adapter process, which no longer exists in Windows 11 25H2 |
|
|
492
|
+
| `build` / `watch` | Build the React sources of the tab (`admin/src`) to `admin/build` |
|
|
493
|
+
| `upload:tab` | Build the tab and write the admin files into the running dev-server instance (works while `dev-server watch` runs) |
|
|
494
|
+
| `test` | Unit tests (`lib/**/*.test.js`) and package tests |
|
|
495
|
+
| `check` | Type check of the adapter (`npx tsc -p admin/tsconfig.json --noEmit` for the tab) |
|
|
496
|
+
| `lint` | ESLint |
|
|
497
|
+
| `translate` | Translate the texts of the adapter |
|
|
498
|
+
| `release` | Create a new release |
|
|
499
|
+
|
|
500
|
+
After changes of `io-package.json`, run `npm run dev-server upload` and restart `npm run dev-server watch`.
|
|
501
|
+
|
|
502
|
+
**The built tab (`admin/build`) is committed**, because installations from GitHub do not run a build.
|
|
503
|
+
After changes in `admin/src`, run `npm run build` and commit `admin/build` as well. The workflow job
|
|
504
|
+
`check-admin-build` fails if it is outdated. The source maps (`admin/build/*.map`) are not committed.
|
|
505
|
+
|
|
506
|
+
| Path | Content |
|
|
507
|
+
|---|---|
|
|
508
|
+
| `main.js` | Adapter: loads the charts, subscribes the inputs, restarts a chart when it is saved |
|
|
509
|
+
| `lib/chart-model.js` | Chart model (shared by adapter and editor) and parser of the transition labels |
|
|
510
|
+
| `lib/chart-runtime.js` | Execution of a chart with XState |
|
|
511
|
+
| `lib/chart-validation.js` | Checks of conditions, actions and function blocks |
|
|
512
|
+
| `admin/src/` | Editor (React, React Flow, CodeMirror) |
|
|
513
|
+
| `admin/jsonConfig.json` | Instance settings |
|
|
514
|
+
|
|
515
|
+
## Disclaimer
|
|
516
|
+
|
|
517
|
+
MATLAB and Stateflow are registered trademarks of The MathWorks, Inc. This adapter is not affiliated
|
|
518
|
+
with, endorsed or sponsored by The MathWorks, Inc.
|
|
519
|
+
|
|
520
|
+
## Changelog
|
|
521
|
+
<!--
|
|
522
|
+
Placeholder for the next version (at the beginning of the line):
|
|
523
|
+
### **WORK IN PROGRESS**
|
|
524
|
+
-->
|
|
525
|
+
### 0.1.1 (2026-10-25)
|
|
526
|
+
* (Igge) renamed from "state-machine" to "statechart", because npm does not accept the old name (too similar to an existing package)
|
|
527
|
+
|
|
528
|
+
### 0.1.0 (2026-10-25)
|
|
529
|
+
* (Igge) initial release
|
|
530
|
+
* (Igge) graphical chart editor in the admin side bar: states, transitions, nested states, function blocks, notes
|
|
531
|
+
* (Igge) transition labels in Stateflow syntax event[condition]{action}, timers with after(n, unit)
|
|
532
|
+
* (Igge) execution with Stateflow semantics (outer before inner transitions, inner transitions)
|
|
533
|
+
* (Igge) charts continue in their state after a restart of the adapter
|
|
534
|
+
* (Igge) per input: wake up on updates or changes, filter by ack, debounce
|
|
535
|
+
* (Igge) helper functions: setState, setStateDelayed, sendTo, isTimeBetween, isDaytime, astroTime
|
|
536
|
+
* (Igge) code editor with syntax highlighting, error checks and completion, generated code view
|
|
537
|
+
* (Igge) undo/redo, copy/paste, import/export of charts as JSON
|
|
538
|
+
|
|
539
|
+
## License
|
|
540
|
+
MIT License
|
|
541
|
+
|
|
542
|
+
Copyright (c) 2026 Igge <mail@igge.org>
|
|
543
|
+
|
|
544
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
545
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
546
|
+
in the Software without restriction, including without limitation the rights
|
|
547
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
548
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
549
|
+
furnished to do so, subject to the following conditions:
|
|
550
|
+
|
|
551
|
+
The above copyright notice and this permission notice shall be included in all
|
|
552
|
+
copies or substantial portions of the Software.
|
|
553
|
+
|
|
554
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
555
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
556
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
557
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
558
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
559
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
560
|
+
SOFTWARE.
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
.react-flow{direction:ltr;--xy-edge-stroke-default: #b1b1b7;--xy-edge-stroke-width-default: 1;--xy-edge-stroke-selected-default: #555;--xy-connectionline-stroke-default: #b1b1b7;--xy-connectionline-stroke-width-default: 1;--xy-attribution-background-color-default: rgba(255, 255, 255, .5);--xy-minimap-background-color-default: #fff;--xy-minimap-mask-background-color-default: rgba(240, 240, 240, .6);--xy-minimap-mask-stroke-color-default: transparent;--xy-minimap-mask-stroke-width-default: 1;--xy-minimap-node-background-color-default: #e2e2e2;--xy-minimap-node-stroke-color-default: transparent;--xy-minimap-node-stroke-width-default: 2;--xy-background-color-default: transparent;--xy-background-pattern-dots-color-default: #91919a;--xy-background-pattern-lines-color-default: #eee;--xy-background-pattern-cross-color-default: #e2e2e2;background-color:var(--xy-background-color, var(--xy-background-color-default));--xy-node-color-default: inherit;--xy-node-border-default: 1px solid #1a192b;--xy-node-background-color-default: #fff;--xy-node-group-background-color-default: rgba(240, 240, 240, .25);--xy-node-boxshadow-hover-default: 0 1px 4px 1px rgba(0, 0, 0, .08);--xy-node-boxshadow-selected-default: 0 0 0 .5px #1a192b;--xy-node-border-radius-default: 3px;--xy-handle-background-color-default: #1a192b;--xy-handle-border-color-default: #fff;--xy-selection-background-color-default: rgba(0, 89, 220, .08);--xy-selection-border-default: 1px dotted rgba(0, 89, 220, .8);--xy-controls-button-background-color-default: #fefefe;--xy-controls-button-background-color-hover-default: #f4f4f4;--xy-controls-button-color-default: inherit;--xy-controls-button-color-hover-default: inherit;--xy-controls-button-border-color-default: #eee;--xy-controls-box-shadow-default: 0 0 2px 1px rgba(0, 0, 0, .08);--xy-edge-label-background-color-default: #ffffff;--xy-edge-label-color-default: inherit;--xy-resize-background-color-default: #3367d9}.react-flow.dark{--xy-edge-stroke-default: #3e3e3e;--xy-edge-stroke-width-default: 1;--xy-edge-stroke-selected-default: #727272;--xy-connectionline-stroke-default: #b1b1b7;--xy-connectionline-stroke-width-default: 1;--xy-attribution-background-color-default: rgba(150, 150, 150, .25);--xy-minimap-background-color-default: #141414;--xy-minimap-mask-background-color-default: rgba(60, 60, 60, .6);--xy-minimap-mask-stroke-color-default: transparent;--xy-minimap-mask-stroke-width-default: 1;--xy-minimap-node-background-color-default: #2b2b2b;--xy-minimap-node-stroke-color-default: transparent;--xy-minimap-node-stroke-width-default: 2;--xy-background-color-default: #141414;--xy-background-pattern-dots-color-default: #555;--xy-background-pattern-lines-color-default: #333;--xy-background-pattern-cross-color-default: #333;--xy-node-color-default: #f8f8f8;--xy-node-border-default: 1px solid #3c3c3c;--xy-node-background-color-default: #1e1e1e;--xy-node-group-background-color-default: rgba(240, 240, 240, .25);--xy-node-boxshadow-hover-default: 0 1px 4px 1px rgba(255, 255, 255, .08);--xy-node-boxshadow-selected-default: 0 0 0 .5px #999;--xy-handle-background-color-default: #bebebe;--xy-handle-border-color-default: #1e1e1e;--xy-selection-background-color-default: rgba(200, 200, 220, .08);--xy-selection-border-default: 1px dotted rgba(200, 200, 220, .8);--xy-controls-button-background-color-default: #2b2b2b;--xy-controls-button-background-color-hover-default: #3e3e3e;--xy-controls-button-color-default: #f8f8f8;--xy-controls-button-color-hover-default: #fff;--xy-controls-button-border-color-default: #5b5b5b;--xy-controls-box-shadow-default: 0 0 2px 1px rgba(0, 0, 0, .08);--xy-edge-label-background-color-default: #141414;--xy-edge-label-color-default: #f8f8f8}.react-flow__background{background-color:var(--xy-background-color-props, var(--xy-background-color, var(--xy-background-color-default)));pointer-events:none;z-index:-1}.react-flow__container{position:absolute;width:100%;height:100%;top:0;left:0}.react-flow__pane{z-index:1;touch-action:none}.react-flow__pane.draggable{cursor:grab}.react-flow__pane.dragging{cursor:grabbing}.react-flow__pane.selection{cursor:pointer}.react-flow__viewport{transform-origin:0 0;z-index:2;pointer-events:none}.react-flow__renderer{z-index:4}.react-flow__selection{z-index:6}.react-flow__nodesselection-rect:focus,.react-flow__nodesselection-rect:focus-visible{outline:none}.react-flow__edge-path{stroke:var(--xy-edge-stroke, var(--xy-edge-stroke-default));stroke-width:var(--xy-edge-stroke-width, var(--xy-edge-stroke-width-default));fill:none}.react-flow__connection-path{stroke:var(--xy-connectionline-stroke, var(--xy-connectionline-stroke-default));stroke-width:var(--xy-connectionline-stroke-width, var(--xy-connectionline-stroke-width-default));fill:none}.react-flow .react-flow__edges{position:absolute}.react-flow .react-flow__edges svg{overflow:visible;position:absolute;pointer-events:none}.react-flow__edge{pointer-events:visibleStroke}.react-flow__edge.selectable{cursor:pointer}.react-flow__edge.animated path{stroke-dasharray:5;animation:dashdraw .5s linear infinite}.react-flow__edge.animated path.react-flow__edge-interaction{stroke-dasharray:none;animation:none}.react-flow__edge.inactive{pointer-events:none}.react-flow__edge.selected,.react-flow__edge:focus,.react-flow__edge:focus-visible{outline:none}.react-flow__edge.selected .react-flow__edge-path,.react-flow__edge.selectable:focus .react-flow__edge-path,.react-flow__edge.selectable:focus-visible .react-flow__edge-path{stroke:var(--xy-edge-stroke-selected, var(--xy-edge-stroke-selected-default))}.react-flow__edge-textwrapper{pointer-events:all}.react-flow__edge .react-flow__edge-text{pointer-events:none;-webkit-user-select:none;-moz-user-select:none;user-select:none}.react-flow__arrowhead polyline{stroke:var(--xy-edge-stroke, var(--xy-edge-stroke-default))}.react-flow__arrowhead polyline.arrowclosed{fill:var(--xy-edge-stroke, var(--xy-edge-stroke-default))}.react-flow__connection{pointer-events:none}.react-flow__connection .animated{stroke-dasharray:5;animation:dashdraw .5s linear infinite}svg.react-flow__connectionline{z-index:1001;overflow:visible;position:absolute}.react-flow__nodes{pointer-events:none;transform-origin:0 0}.react-flow__node{position:absolute;-webkit-user-select:none;-moz-user-select:none;user-select:none;pointer-events:all;transform-origin:0 0;box-sizing:border-box;cursor:default}.react-flow__node.selectable{cursor:pointer}.react-flow__node.draggable{cursor:grab;pointer-events:all}.react-flow__node.draggable.dragging{cursor:grabbing}.react-flow__nodesselection{z-index:3;transform-origin:left top;pointer-events:none}.react-flow__nodesselection-rect{position:absolute;pointer-events:all;cursor:grab}.react-flow__handle{position:absolute;pointer-events:none;min-width:5px;min-height:5px;width:6px;height:6px;background-color:var(--xy-handle-background-color, var(--xy-handle-background-color-default));border:1px solid var(--xy-handle-border-color, var(--xy-handle-border-color-default));border-radius:100%}.react-flow__handle.connectingfrom{pointer-events:all}.react-flow__handle.connectionindicator{pointer-events:all;cursor:crosshair}.react-flow__handle-bottom{top:auto;left:50%;bottom:0;transform:translate(-50%,50%)}.react-flow__handle-top{top:0;left:50%;transform:translate(-50%,-50%)}.react-flow__handle-left{top:50%;left:0;transform:translate(-50%,-50%)}.react-flow__handle-right{top:50%;right:0;transform:translate(50%,-50%)}.react-flow__edgeupdater{cursor:move;pointer-events:all}.react-flow__pane.selection .react-flow__panel{pointer-events:none}.react-flow__panel{position:absolute;z-index:5;margin:15px}.react-flow__panel.top{top:0}.react-flow__panel.bottom{bottom:0}.react-flow__panel.top.center,.react-flow__panel.bottom.center{left:50%;transform:translate(-15px) translate(-50%)}.react-flow__panel.left{left:0}.react-flow__panel.right{right:0}.react-flow__panel.left.center,.react-flow__panel.right.center{top:50%;transform:translateY(-15px) translateY(-50%)}.react-flow__attribution{font-size:10px;background:var(--xy-attribution-background-color, var(--xy-attribution-background-color-default));padding:2px 3px;margin:0}.react-flow__attribution a{text-decoration:none;color:#999}@keyframes dashdraw{0%{stroke-dashoffset:10}}.react-flow__edgelabel-renderer{position:absolute;width:100%;height:100%;pointer-events:none;-webkit-user-select:none;-moz-user-select:none;user-select:none;left:0;top:0}.react-flow__viewport-portal{position:absolute;width:100%;height:100%;left:0;top:0;-webkit-user-select:none;-moz-user-select:none;user-select:none}.react-flow__minimap{background:var( --xy-minimap-background-color-props, var(--xy-minimap-background-color, var(--xy-minimap-background-color-default)) )}.react-flow__minimap-svg{display:block}.react-flow__minimap-mask{fill:var( --xy-minimap-mask-background-color-props, var(--xy-minimap-mask-background-color, var(--xy-minimap-mask-background-color-default)) );stroke:var( --xy-minimap-mask-stroke-color-props, var(--xy-minimap-mask-stroke-color, var(--xy-minimap-mask-stroke-color-default)) );stroke-width:var( --xy-minimap-mask-stroke-width-props, var(--xy-minimap-mask-stroke-width, var(--xy-minimap-mask-stroke-width-default)) )}.react-flow__minimap-node{fill:var( --xy-minimap-node-background-color-props, var(--xy-minimap-node-background-color, var(--xy-minimap-node-background-color-default)) );stroke:var( --xy-minimap-node-stroke-color-props, var(--xy-minimap-node-stroke-color, var(--xy-minimap-node-stroke-color-default)) );stroke-width:var( --xy-minimap-node-stroke-width-props, var(--xy-minimap-node-stroke-width, var(--xy-minimap-node-stroke-width-default)) )}.react-flow__background-pattern.dots{fill:var( --xy-background-pattern-color-props, var(--xy-background-pattern-color, var(--xy-background-pattern-dots-color-default)) )}.react-flow__background-pattern.lines{stroke:var( --xy-background-pattern-color-props, var(--xy-background-pattern-color, var(--xy-background-pattern-lines-color-default)) )}.react-flow__background-pattern.cross{stroke:var( --xy-background-pattern-color-props, var(--xy-background-pattern-color, var(--xy-background-pattern-cross-color-default)) )}.react-flow__controls{display:flex;flex-direction:column;box-shadow:var(--xy-controls-box-shadow, var(--xy-controls-box-shadow-default))}.react-flow__controls.horizontal{flex-direction:row}.react-flow__controls-button{display:flex;justify-content:center;align-items:center;height:26px;width:26px;padding:4px;border:none;background:var(--xy-controls-button-background-color, var(--xy-controls-button-background-color-default));border-bottom:1px solid var( --xy-controls-button-border-color-props, var(--xy-controls-button-border-color, var(--xy-controls-button-border-color-default)) );color:var( --xy-controls-button-color-props, var(--xy-controls-button-color, var(--xy-controls-button-color-default)) );cursor:pointer;-webkit-user-select:none;-moz-user-select:none;user-select:none}.react-flow__controls-button svg{width:100%;max-width:12px;max-height:12px;fill:currentColor}.react-flow__edge.updating .react-flow__edge-path{stroke:#777}.react-flow__edge-text{font-size:10px}.react-flow__node.selectable:focus,.react-flow__node.selectable:focus-visible{outline:none}.react-flow__node-input,.react-flow__node-default,.react-flow__node-output,.react-flow__node-group{padding:10px;border-radius:var(--xy-node-border-radius, var(--xy-node-border-radius-default));width:150px;font-size:12px;color:var(--xy-node-color, var(--xy-node-color-default));text-align:center;border:var(--xy-node-border, var(--xy-node-border-default));background-color:var(--xy-node-background-color, var(--xy-node-background-color-default))}.react-flow__node-input.selectable:hover,.react-flow__node-default.selectable:hover,.react-flow__node-output.selectable:hover,.react-flow__node-group.selectable:hover{box-shadow:var(--xy-node-boxshadow-hover, var(--xy-node-boxshadow-hover-default))}.react-flow__node-input.selectable.selected,.react-flow__node-input.selectable:focus,.react-flow__node-input.selectable:focus-visible,.react-flow__node-default.selectable.selected,.react-flow__node-default.selectable:focus,.react-flow__node-default.selectable:focus-visible,.react-flow__node-output.selectable.selected,.react-flow__node-output.selectable:focus,.react-flow__node-output.selectable:focus-visible,.react-flow__node-group.selectable.selected,.react-flow__node-group.selectable:focus,.react-flow__node-group.selectable:focus-visible{box-shadow:var(--xy-node-boxshadow-selected, var(--xy-node-boxshadow-selected-default))}.react-flow__node-group{background-color:var(--xy-node-group-background-color, var(--xy-node-group-background-color-default))}.react-flow__nodesselection-rect,.react-flow__selection{background:var(--xy-selection-background-color, var(--xy-selection-background-color-default));border:var(--xy-selection-border, var(--xy-selection-border-default))}.react-flow__nodesselection-rect:focus,.react-flow__nodesselection-rect:focus-visible,.react-flow__selection:focus,.react-flow__selection:focus-visible{outline:none}.react-flow__controls-button:hover{background:var( --xy-controls-button-background-color-hover-props, var(--xy-controls-button-background-color-hover, var(--xy-controls-button-background-color-hover-default)) );color:var( --xy-controls-button-color-hover-props, var(--xy-controls-button-color-hover, var(--xy-controls-button-color-hover-default)) )}.react-flow__controls-button:disabled{pointer-events:none}.react-flow__controls-button:disabled svg{fill-opacity:.4}.react-flow__controls-button:last-child{border-bottom:none}.react-flow__controls.horizontal .react-flow__controls-button{border-bottom:none;border-right:1px solid var( --xy-controls-button-border-color-props, var(--xy-controls-button-border-color, var(--xy-controls-button-border-color-default)) )}.react-flow__controls.horizontal .react-flow__controls-button:last-child{border-right:none}.react-flow__resize-control{position:absolute}.react-flow__resize-control.left,.react-flow__resize-control.right{cursor:ew-resize}.react-flow__resize-control.top,.react-flow__resize-control.bottom{cursor:ns-resize}.react-flow__resize-control.top.left,.react-flow__resize-control.bottom.right{cursor:nwse-resize}.react-flow__resize-control.bottom.left,.react-flow__resize-control.top.right{cursor:nesw-resize}.react-flow__resize-control.handle{width:5px;height:5px;border:1px solid #fff;border-radius:1px;background-color:var(--xy-resize-background-color, var(--xy-resize-background-color-default));translate:-50% -50%}.react-flow__resize-control.handle.left{left:0;top:50%}.react-flow__resize-control.handle.right{left:100%;top:50%}.react-flow__resize-control.handle.top{left:50%;top:0}.react-flow__resize-control.handle.bottom{left:50%;top:100%}.react-flow__resize-control.handle.top.left,.react-flow__resize-control.handle.bottom.left{left:0}.react-flow__resize-control.handle.top.right,.react-flow__resize-control.handle.bottom.right{left:100%}.react-flow__resize-control.line{border-color:var(--xy-resize-background-color, var(--xy-resize-background-color-default));border-width:0;border-style:solid}.react-flow__resize-control.line.left,.react-flow__resize-control.line.right{width:1px;transform:translate(-50%);top:0;height:100%}.react-flow__resize-control.line.left{left:0;border-left-width:1px}.react-flow__resize-control.line.right{left:100%;border-right-width:1px}.react-flow__resize-control.line.top,.react-flow__resize-control.line.bottom{height:1px;transform:translateY(-50%);left:0;width:100%}.react-flow__resize-control.line.top{top:0;border-top-width:1px}.react-flow__resize-control.line.bottom{border-bottom-width:1px;top:100%}.react-flow__edge-textbg{fill:var(--xy-edge-label-background-color, var(--xy-edge-label-background-color-default))}.react-flow__edge-text{fill:var(--xy-edge-label-color, var(--xy-edge-label-color-default))}
|
|
2
|
+
/*# sourceMappingURL=tab.css.map */
|