iobroker.statechart 0.0.0-stage → 0.2.0

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/README.md CHANGED
@@ -1,3 +1,669 @@
1
- # Temporary Holding Version
1
+ ![Logo](admin/statechart.png)
2
+ # ioBroker.statechart
2
3
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
4
+ [![NPM version](https://img.shields.io/npm/v/iobroker.statechart.svg)](https://www.npmjs.com/package/iobroker.statechart)
5
+ [![Downloads](https://img.shields.io/npm/dm/iobroker.statechart.svg)](https://www.npmjs.com/package/iobroker.statechart)
6
+ ![Number of Installations](https://iobroker.live/badges/statechart-installed.svg)
7
+ ![Current version in stable repository](https://iobroker.live/badges/statechart-stable.svg)
8
+
9
+ [![NPM](https://nodei.co/npm/iobroker.statechart.png?downloads=true)](https://nodei.co/npm/iobroker.statechart/)
10
+
11
+ **Tests:** ![Test and Release](https://github.com/IggePop/ioBroker.statechart/workflows/Test%20and%20Release/badge.svg)
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, the current values of the variables are shown, taken
34
+ transitions light up, and a history shows when which transition was taken and why
35
+ - Code editor with syntax highlighting, error checking (e.g. unknown variables) and completion
36
+ - Several independent charts per instance
37
+ - Import and export of charts as JSON files
38
+ - Comments on states, transitions and function blocks, and notes on the canvas
39
+ - Per input: wake up on every update or only on changes, filter by ack, debounce
40
+ - Helper functions: setState, setStateDelayed, sendTo, isTimeBetween, isDaytime, astroTime, change detection (hasChanged...)
41
+ - 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
42
+ - **Show code**: the JavaScript generated from the chart, showing exactly how the adapter executes it
43
+
44
+ ### Installation
45
+
46
+ The adapter is not in the ioBroker repositories yet, but can be installed directly from GitHub:
47
+
48
+ - **Admin:** tab **Adapters** → icon **Install from custom URL** (GitHub cat) → tab **Custom** →
49
+ URL `https://github.com/IggePop/ioBroker.statechart` → **Install**
50
+ - **or on the console** of the ioBroker system:
51
+ ```bash
52
+ iobroker url https://github.com/IggePop/ioBroker.statechart
53
+ ```
54
+
55
+ Then add an instance of **State Machine**. This installs the current development version, which may
56
+ still change incompatibly.
57
+
58
+ Requirements: Node.js 22 or newer, js-controller 6.0.11 or newer, admin 7.6.17 or newer.
59
+
60
+ ### Getting started
61
+
62
+ 1. Open **State Machine** in the admin side bar.
63
+ 2. Create a chart with **+** in the list on the left.
64
+ 3. Define the variables in the tab **Variables** on the right:
65
+ - **Input**: read from an ioBroker state, every change wakes up the chart
66
+ - **Output**: written to an ioBroker state when an action changes it
67
+ - **Local**: only exists inside the chart, with an initial value
68
+ 4. Add states with **Add state**. The state marked with the dot and arrow is the initial state
69
+ (can be changed in the properties of a state).
70
+ 5. Draw transitions by dragging from the border of a state to another state (or to itself). The arrows start and
71
+ end where the states face each other and follow them when they are moved. To fix an end at another place,
72
+ select the transition and drag the round grip at the end along the border (double click: automatic again). To
73
+ move a transition to another state, drop the grip on that state: its label and all other properties are kept.
74
+ 6. Select a state or transition (or double click it) to edit its actions or label on the right.
75
+ 7. Click **Save**. New charts are paused: start the chart with ▶ in the chart list. After saving, the adapter
76
+ restarts a running chart immediately.
77
+
78
+ ### Example: light with motion detector
79
+
80
+ | Variable | Scope | State |
81
+ |---|---|---|
82
+ | `motion` | Input | e.g. `hm-rpc.0.ABC123.1.MOTION` |
83
+ | `light` | Output | e.g. `hue.0.hallway.on` |
84
+
85
+ | State | entry |
86
+ |---|---|
87
+ | Off (initial state) | `light = false;` |
88
+ | On | `light = true;` |
89
+
90
+ | Transition | Label |
91
+ |---|---|
92
+ | Off → On | `motion[motion == true]` |
93
+ | On → Off | `after(2, min)[!motion]` |
94
+
95
+ ### Transition labels
96
+
97
+ ```
98
+ event[condition]{action}
99
+ ```
100
+
101
+ All parts are optional and may span several lines.
102
+
103
+ | Part | Meaning | Example |
104
+ |---|---|---|
105
+ | `event` | Name of an input: the transition is only checked when this input changes | `motion` |
106
+ | `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)` |
107
+ | `[condition]` | JavaScript expression, the transition is only taken if it is true | `[temp > 21 && !window]` |
108
+ | `{action}` | JavaScript, executed when the transition is taken | `{count++; light = true;}` |
109
+
110
+ Without event, a transition is checked whenever any input changes. If a state has several outgoing
111
+ transitions, they are checked in the order of their **priority** (shown at the arrow).
112
+
113
+ ### When inputs wake up the chart
114
+
115
+ Every input has three settings in the tab **Variables**:
116
+
117
+ | Setting | Values | Default |
118
+ |---|---|---|
119
+ | **Wake up on** | every update / value changes only | every update |
120
+ | **Acknowledged (ack)** | any / confirmed values only (`ack=true`) / commands only (`ack=false`) | any |
121
+ | **Debounce (ms)** | the chart wakes up only after the input had no update for this time, with its last value | 0 (off) |
122
+
123
+ Updates filtered out by ack do not change the variable, so it e.g. only contains confirmed values.
124
+
125
+ ### Actions
126
+
127
+ Actions and conditions are JavaScript. All variables of the chart can be used directly by their name,
128
+ plus `Math`, `Date`, `JSON` etc. and `log("text")` to write to the ioBroker log.
129
+
130
+ | Action | Executed |
131
+ |---|---|
132
+ | `entry` | when the state is entered |
133
+ | `during` | on every wakeup of the chart while the state is active and none of its outgoing transitions is taken |
134
+ | `exit` | when the state is left |
135
+
136
+ ### Nested states
137
+
138
+ Drop a state into another state to make it a **sub state**, drag it out again to undo it. While dragging,
139
+ the state it would be moved into is highlighted. Every level has its own initial state (dot with arrow).
140
+
141
+ - Entering a super state also enters its initial sub state (or the sub state the transition points to).
142
+ Leaving it leaves the active sub state first.
143
+ - On every wakeup, each active state is processed in this order, starting with the outermost one:
144
+ 1. transitions leaving the state, 2. its `during` action, 3. its **inner transitions**, 4. its active sub state.
145
+ So the transitions of a super state have priority over those of its sub states.
146
+ - A transition from a state to one of its own sub states is an **inner transition** (dot at its start):
147
+ the state stays active, only its active sub state is left, its own `entry`/`exit` are not executed.
148
+ With the option **Leave and enter the parent state again**, it is left and entered again instead.
149
+
150
+ ### Helper functions
151
+
152
+ In conditions, actions and function blocks:
153
+
154
+ | Function | Purpose |
155
+ |---|---|
156
+ | `log(message, level)` | writes to the ioBroker log, level `"debug"`, `"info"` (default), `"warn"` or `"error"` |
157
+ | `setState(id, value, ack)` | writes any ioBroker state, `ack` is `false` by default |
158
+ | `setStateDelayed(id, value, delayMs, ack)` | writes after a delay, cancelled when the chart stops |
159
+ | `sendTo(instance, command, message)` | sends a message to an adapter, e.g. `sendTo("telegram.0", "send", { text: "Window open" })` |
160
+ | `isTimeBetween("22:00", "06:00")` | true if the current time is in the range, also over midnight |
161
+ | `isDaytime()` | true between sunrise and sunset |
162
+ | `astroTime("sunset", offsetMinutes)` | time of an astro event today as `Date`: `sunrise`, `sunset`, `dawn`, `dusk`, `solarNoon`, ... |
163
+ | `hasChanged(variable)` | true if the variable has changed, see below |
164
+ | `hasChangedFrom(variable, value)` | true if the variable has changed and its previous value was `value` |
165
+ | `hasChangedTo(variable, value)` | true if the variable has changed to `value`, e.g. `hasChangedTo(button, true)` for a rising edge |
166
+
167
+ `isDaytime()` and `astroTime()` use the location of the ioBroker system settings. To read other states, add
168
+ them as inputs: their variables are always up to date and wake up the chart. Time based functions do not wake up
169
+ the chart by themselves, they are evaluated when it wakes up.
170
+
171
+ **Change detection** works like in Stateflow: it compares the value of the variable at the start of the current
172
+ wakeup with its value at the start of the previous wakeup. Inputs, outputs and locals can be checked. An update
173
+ with the same value is no change, neither is a wakeup by another input or a timer, so `[hasChangedTo(button, true)]`
174
+ is only true once per press of the button. On the wakeup after the start nothing has changed yet. A change of a
175
+ local by an action is seen on the next wakeup, also in a super step. Objects and arrays are compared by their
176
+ contents.
177
+
178
+ ### Function blocks
179
+
180
+ **Add function** creates a block for your own JavaScript. It is executed once when the chart starts,
181
+ all functions and constants declared in it can be used in every condition and action of the chart:
182
+
183
+ ```js
184
+ const LIMIT = 22;
185
+
186
+ function tooWarm(temp) {
187
+ return temp > LIMIT;
188
+ }
189
+ ```
190
+
191
+ Transition label: `[tooWarm(temperature)]{heating = false;}`
192
+
193
+ ### Comments and notes
194
+
195
+ - **Comment:** states, transitions, function blocks and the chart itself have a field **Comment** in their properties.
196
+ A speech bubble on the canvas shows that there is a comment, the comment is shown as tooltip.
197
+ - **Notes** (icon **Add note** in the toolbar) are free texts on the canvas, like sticky notes. Drop a note into a
198
+ state, so it moves together with it.
199
+
200
+ Comments and notes have no influence on the execution. They are included in the generated code and in exported
201
+ files, and changing them does not prevent restoring the state after a restart.
202
+
203
+ ### Watching a running chart
204
+
205
+ While a chart runs, the editor shows what it does:
206
+
207
+ - The **active states** have a green border.
208
+ - **Live values** (panel at the top left of the canvas, switched on and off with the last icon in the toolbar):
209
+ the current values of all variables. They are also shown below each variable in the tab **Variables**.
210
+ - A **transition lights up** for a moment when the chart takes it.
211
+ - The tab **History** lists the starts of the chart and the transitions taken, newest first: when, from which state
212
+ to which, triggered by which input or by a timer (`after`), and which states were active afterwards. Click a
213
+ transition to select it in the chart. The adapter keeps the last 200 events, also when the editor is closed and
214
+ after a restart. **Clear history** removes them.
215
+
216
+ The live values and the history come from the running chart, so they refer to the saved chart, not to unsaved
217
+ changes in the editor.
218
+
219
+ ### Import and export
220
+
221
+ - **Export chart** (last icon in the toolbar of the editor) saves the chart as JSON file, including unsaved changes.
222
+ - **Export all charts** (icon above the chart list) saves all saved charts in one file.
223
+ - **Import charts** (icon above the chart list) adds the charts of such a file as new charts, existing charts are not
224
+ changed. The file is checked before (structure, IDs, references between states and transitions).
225
+
226
+ Imported charts are **paused**: their variables may refer to states of another system. Check them in the tab
227
+ **Variables**, then start the chart with the button in the chart list.
228
+
229
+ ### States created by the adapter
230
+
231
+ For every chart, the adapter creates below `statechart.<instance>.charts.<chart>`:
232
+
233
+ | State | Content |
234
+ |---|---|
235
+ | `activeState` | Name of the active state, nested states as `Parent.Child` |
236
+ | `activeStateIds` | IDs of all active states as JSON |
237
+ | `snapshot` | Stored state of the chart (active states, when they were entered, locals), see below |
238
+ | `values` | Current values of all variables as JSON, `{}` if the chart does not run |
239
+ | `history` | The last 200 events (starts and transitions taken) as JSON. Writing it without ack clears it |
240
+
241
+ The chart itself is stored in the object `statechart.<instance>.charts.<chart>`.
242
+
243
+ ### Restart of the adapter
244
+
245
+ After a restart of the adapter or of ioBroker, every chart continues in the states which were active
246
+ before, with its local variables. The entry actions are not executed again, and timers continue with
247
+ their remaining time. A timer which expired while the adapter was stopped is taken right after the start.
248
+ Like after every start, the chart then wakes up once, so changes of the inputs during the stop are
249
+ noticed.
250
+
251
+ The chart starts in its initial state instead if it was changed meanwhile (positions in the editor do not
252
+ count), after saving it in the editor, after pausing it, and if the option **Restore the state after a
253
+ restart** in the chart properties is switched off. Variables declared in function blocks are not restored.
254
+
255
+ ### Execution model and differences to other statechart tools
256
+
257
+ - The adapter uses [XState](https://stately.ai/docs/xstate) to execute the charts.
258
+ - Every change of an input wakes up the chart once, as well as an expiring `after(...)` and the start of the chart
259
+ (also after restoring it). Transitions without event, e.g. without label, are checked on every wakeup, so they
260
+ are taken right after the start if their condition is true.
261
+ - By default, only one transition is taken per wakeup, a following one waits for the next wakeup. With the
262
+ chart option **Take transitions until none is valid (super step)**, the chart is checked again right after a
263
+ transition, until no transition is valid anymore, like the super step semantics of Stateflow. The event of an
264
+ input only counts for the first transition of a wakeup, during actions may run several times. To protect against
265
+ loops, at most 100 transitions are taken per wakeup, then a warning is logged.
266
+ - `{action}` is executed while the transition is taken, i.e. after `exit` of the old and before `entry` of
267
+ the new state. A separate condition action and `/action` are not supported (yet).
268
+ - Transitions are selected like in Stateflow (outer before inner, see above), XState only executes them.
269
+ Pure XState would check the transitions of sub states first.
270
+ - Temporal logic is limited to `after(n, msec|sec|min)`, measured in real time.
271
+ - Conditions and actions use JavaScript, not MATLAB/C syntax.
272
+
273
+ ### Limitations
274
+
275
+ - Parallel states (AND), history junctions and connective junctions are not supported yet.
276
+ - Deleting a super state also deletes its sub states.
277
+ - Unsaved changes are lost when you select another chart.
278
+ - Conditions and actions run in a separate JavaScript context with a time limit of 1 second, but this
279
+ is **no security sandbox**: everyone who may edit charts can execute any code in the adapter,
280
+ just like with the JavaScript adapter.
281
+
282
+ ---
283
+
284
+ <a id="deutsch"></a>
285
+
286
+ ## Zustandsautomaten-Adapter für ioBroker
287
+
288
+ Bilde deine Automatisierungen als **Zustandsautomaten** (Statecharts) in einem grafischen Editor ab,
289
+ ähnlich den Chart-Editoren aus der modellbasierten Entwicklung. Zustände, Übergänge mit Events,
290
+ Bedingungen und Aktionen, Timer und Funktionsblöcke werden im ioBroker-Admin gezeichnet und vom
291
+ Adapter ausgeführt.
292
+
293
+ ### Funktionen
294
+
295
+ - Grafischer Editor als eigener Eintrag in der Seitenleiste des Admins
296
+ - Zustände mit `entry`-, `during`- und `exit`-Aktionen
297
+ - Übergänge mit Beschriftungen in der bekannten Syntax `event[bedingung]{aktion}`
298
+ - Timer: `after(5, sec)`
299
+ - Funktionsblöcke mit eigenen JavaScript-Funktionen, nutzbar in allen Bedingungen und Aktionen
300
+ - Eingänge und Ausgänge, die mit beliebigen ioBroker-Datenpunkten verbunden sind
301
+ - Live-Ansicht: Der aktive Zustand wird im Editor hervorgehoben, die aktuellen Werte der Variablen werden angezeigt,
302
+ genommene Übergänge leuchten auf, und ein Verlauf zeigt, wann welcher Übergang genommen wurde und warum
303
+ - Code-Editor mit Syntax-Hervorhebung, Fehlerprüfung (z.B. unbekannte Variablen) und Autovervollständigung
304
+ - Mehrere unabhängige Charts pro Instanz
305
+ - Import und Export von Charts als JSON-Dateien
306
+ - Kommentare zu Zuständen, Übergängen und Funktionsblöcken sowie Notizen auf der Zeichenfläche
307
+ - Pro Eingang: Aufwecken bei jeder Aktualisierung oder nur bei Änderung, Filter nach ack, Entprellen
308
+ - Hilfsfunktionen: setState, setStateDelayed, sendTo, isTimeBetween, isDaytime, astroTime, Änderungserkennung (hasChanged...)
309
+ - 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
310
+ - **Code anzeigen**: das aus dem Chart generierte JavaScript, das genau zeigt, wie der Adapter es ausführt
311
+
312
+ ### Installation
313
+
314
+ Der Adapter ist noch nicht in den ioBroker-Repositories, lässt sich aber direkt von GitHub installieren:
315
+
316
+ - **Admin:** Reiter **Adapter** → Symbol **Aus eigener URL installieren** (GitHub-Katze) → Reiter
317
+ **Benutzerdefiniert** → URL `https://github.com/IggePop/ioBroker.statechart` → **Installieren**
318
+ - **oder auf der Konsole** des ioBroker-Systems:
319
+ ```bash
320
+ iobroker url https://github.com/IggePop/ioBroker.statechart
321
+ ```
322
+
323
+ Danach eine Instanz von **Zustandsmaschine** hinzufügen. Installiert wird der aktuelle
324
+ Entwicklungsstand, der sich noch inkompatibel ändern kann.
325
+
326
+ Voraussetzungen: Node.js 22 oder neuer, js-controller 6.0.11 oder neuer, Admin 7.6.17 oder neuer.
327
+
328
+ ### Erste Schritte
329
+
330
+ 1. In der Seitenleiste des Admins **Zustandsmaschine** öffnen.
331
+ 2. Links in der Liste mit **+** ein Chart anlegen.
332
+ 3. Rechts im Reiter **Variablen** die Variablen festlegen:
333
+ - **Eingang**: wird aus einem ioBroker-Datenpunkt gelesen, jede Änderung weckt das Chart auf
334
+ - **Ausgang**: wird in einen ioBroker-Datenpunkt geschrieben, wenn eine Aktion ihn ändert
335
+ - **Lokal**: gibt es nur im Chart, mit einem Startwert
336
+ 4. Mit **Zustand hinzufügen** Zustände anlegen. Der Zustand mit Punkt und Pfeil ist der Startzustand
337
+ (änderbar in den Eigenschaften eines Zustands).
338
+ 5. Übergänge zeichnen: vom Rand eines Zustands zu einem anderen Zustand (oder zu sich selbst) ziehen. Die Pfeile
339
+ beginnen und enden dort, wo sich die Zustände gegenüberliegen, und folgen ihnen beim Verschieben. Um ein Ende
340
+ an einer anderen Stelle festzulegen, den Übergang auswählen und den runden Griff am Ende am Rand entlang ziehen
341
+ (Doppelklick: wieder automatisch). Um einen Übergang an einen anderen Zustand umzuhängen, den Griff auf diesen
342
+ Zustand ziehen: Beschriftung und alle anderen Eigenschaften bleiben erhalten.
343
+ 6. Einen Zustand oder Übergang anklicken (oder doppelklicken), um rechts seine Aktionen bzw. seine
344
+ Beschriftung zu bearbeiten.
345
+ 7. **Speichern** klicken. Neue Charts sind angehalten: Starte das Chart mit ▶ in der Chart-Liste. Nach dem
346
+ Speichern startet der Adapter ein laufendes Chart sofort neu.
347
+
348
+ ### Beispiel: Licht mit Bewegungsmelder
349
+
350
+ | Variable | Art | Datenpunkt |
351
+ |---|---|---|
352
+ | `motion` | Eingang | z.B. `hm-rpc.0.ABC123.1.MOTION` |
353
+ | `light` | Ausgang | z.B. `hue.0.flur.on` |
354
+
355
+ | Zustand | entry |
356
+ |---|---|
357
+ | Aus (Startzustand) | `light = false;` |
358
+ | An | `light = true;` |
359
+
360
+ | Übergang | Beschriftung |
361
+ |---|---|
362
+ | Aus → An | `motion[motion == true]` |
363
+ | An → Aus | `after(2, min)[!motion]` |
364
+
365
+ ### Beschriftung von Übergängen
366
+
367
+ ```
368
+ event[bedingung]{aktion}
369
+ ```
370
+
371
+ Alle Teile sind optional und dürfen über mehrere Zeilen gehen.
372
+
373
+ | Teil | Bedeutung | Beispiel |
374
+ |---|---|---|
375
+ | `event` | Name eines Eingangs: Der Übergang wird nur geprüft, wenn sich dieser Eingang ändert | `motion` |
376
+ | `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)` |
377
+ | `[bedingung]` | JavaScript-Ausdruck, der Übergang wird nur genommen, wenn er wahr ist | `[temp > 21 && !fenster]` |
378
+ | `{aktion}` | JavaScript, wird ausgeführt, wenn der Übergang genommen wird | `{zaehler++; licht = true;}` |
379
+
380
+ Ohne Event wird ein Übergang bei jeder Änderung eines beliebigen Eingangs geprüft. Hat ein Zustand
381
+ mehrere ausgehende Übergänge, werden sie in der Reihenfolge ihrer **Priorität** geprüft (steht am Pfeil).
382
+
383
+ ### Wann Eingänge das Chart aufwecken
384
+
385
+ Jeder Eingang hat im Reiter **Variablen** drei Einstellungen:
386
+
387
+ | Einstellung | Werte | Standard |
388
+ |---|---|---|
389
+ | **Aufwecken bei** | jeder Aktualisierung / nur bei Wertänderung | jeder Aktualisierung |
390
+ | **Bestätigt (ack)** | egal / nur bestätigte Werte (`ack=true`) / nur Befehle (`ack=false`) | egal |
391
+ | **Entprellen (ms)** | das Chart wird erst geweckt, wenn der Eingang so lange keine Aktualisierung bekommen hat, mit dem letzten Wert | 0 (aus) |
392
+
393
+ Aktualisierungen, die wegen ack aussortiert werden, ändern die Variable nicht. Sie enthält dann z.B. nur bestätigte Werte.
394
+
395
+ ### Aktionen
396
+
397
+ Aktionen und Bedingungen sind JavaScript. Alle Variablen des Charts sind direkt über ihren Namen
398
+ verfügbar, dazu `Math`, `Date`, `JSON` usw. sowie `log("Text")` zum Schreiben ins ioBroker-Log.
399
+
400
+ | Aktion | Wird ausgeführt |
401
+ |---|---|
402
+ | `entry` | beim Betreten des Zustands |
403
+ | `during` | bei jedem Aufwecken des Charts, solange der Zustand aktiv ist und keiner seiner ausgehenden Übergänge genommen wird |
404
+ | `exit` | beim Verlassen des Zustands |
405
+
406
+ ### Verschachtelte Zustände
407
+
408
+ Lass einen Zustand über einem anderen los, um ihn zum **Unterzustand** zu machen, und zieh ihn wieder
409
+ heraus, um das rückgängig zu machen. Beim Ziehen wird der Zustand hervorgehoben, in den er verschoben
410
+ würde. Jede Ebene hat ihren eigenen Startzustand (Punkt mit Pfeil).
411
+
412
+ - Beim Betreten eines Superstates wird auch sein Start-Unterzustand betreten (oder der Unterzustand,
413
+ auf den der Übergang zeigt). Beim Verlassen wird zuerst der aktive Unterzustand verlassen.
414
+ - Bei jedem Aufwecken wird jeder aktive Zustand in dieser Reihenfolge bearbeitet, beginnend mit dem äußersten:
415
+ 1. Übergänge, die den Zustand verlassen, 2. seine `during`-Aktion, 3. seine **inneren Übergänge**,
416
+ 4. sein aktiver Unterzustand. Die Übergänge eines Superstates haben also Vorrang vor denen seiner Unterzustände.
417
+ - Ein Übergang von einem Zustand zu einem seiner eigenen Unterzustände ist ein **innerer Übergang**
418
+ (Punkt am Anfang): Der Zustand bleibt aktiv, nur sein aktiver Unterzustand wird verlassen, seine
419
+ eigenen `entry`/`exit` laufen nicht. Mit der Option **Elternzustand verlassen und neu betreten** wird
420
+ er stattdessen verlassen und neu betreten.
421
+
422
+ ### Hilfsfunktionen
423
+
424
+ In Bedingungen, Aktionen und Funktionsblöcken:
425
+
426
+ | Funktion | Zweck |
427
+ |---|---|
428
+ | `log(text, stufe)` | schreibt ins ioBroker-Log, Stufe `"debug"`, `"info"` (Standard), `"warn"` oder `"error"` |
429
+ | `setState(id, wert, ack)` | schreibt einen beliebigen Datenpunkt, `ack` ist standardmäßig `false` |
430
+ | `setStateDelayed(id, wert, ms, ack)` | schreibt verzögert, wird beim Anhalten des Charts verworfen |
431
+ | `sendTo(instanz, befehl, nachricht)` | sendet eine Nachricht an einen Adapter, z.B. `sendTo("telegram.0", "send", { text: "Fenster offen" })` |
432
+ | `isTimeBetween("22:00", "06:00")` | wahr, wenn die aktuelle Uhrzeit im Bereich liegt, auch über Mitternacht |
433
+ | `isDaytime()` | wahr zwischen Sonnenaufgang und Sonnenuntergang |
434
+ | `astroTime("sunset", minuten)` | Zeitpunkt eines Astro-Ereignisses heute als `Date`: `sunrise`, `sunset`, `dawn`, `dusk`, `solarNoon`, ... |
435
+ | `hasChanged(variable)` | wahr, wenn sich die Variable geändert hat, siehe unten |
436
+ | `hasChangedFrom(variable, wert)` | wahr, wenn sich die Variable geändert hat und vorher `wert` war |
437
+ | `hasChangedTo(variable, wert)` | wahr, wenn sich die Variable auf `wert` geändert hat, z.B. `hasChangedTo(taster, true)` für eine steigende Flanke |
438
+
439
+ `isDaytime()` und `astroTime()` nutzen den Standort aus den ioBroker-Systemeinstellungen. Andere Datenpunkte liest
440
+ man über Eingänge: Ihre Variablen sind immer aktuell und wecken das Chart. Zeitabhängige Funktionen wecken das
441
+ Chart nicht von selbst, sie werden beim Aufwecken ausgewertet.
442
+
443
+ Die **Änderungserkennung** funktioniert wie in Stateflow: Sie vergleicht den Wert der Variablen zu Beginn des
444
+ aktuellen Aufweckens mit ihrem Wert zu Beginn des vorherigen. Eingänge, Ausgänge und lokale Variablen lassen sich
445
+ prüfen. Eine Aktualisierung mit demselben Wert ist keine Änderung, ebenso wenig ein Aufwecken durch einen anderen
446
+ Eingang oder einen Timer. `[hasChangedTo(taster, true)]` ist also nur einmal pro Tastendruck wahr. Beim Aufwecken
447
+ nach dem Start hat sich noch nichts geändert. Ändert eine Aktion eine lokale Variable, sieht man das erst beim
448
+ nächsten Aufwecken, auch im Super-Step. Objekte und Arrays werden nach ihrem Inhalt verglichen.
449
+
450
+ ### Funktionsblöcke
451
+
452
+ **Funktion hinzufügen** legt einen Block für eigenes JavaScript an. Er wird beim Start des Charts
453
+ einmal ausgeführt. Alle darin deklarierten Funktionen und Konstanten können in jeder Bedingung und
454
+ Aktion des Charts verwendet werden:
455
+
456
+ ```js
457
+ const LIMIT = 22;
458
+
459
+ function zuWarm(temp) {
460
+ return temp > LIMIT;
461
+ }
462
+ ```
463
+
464
+ Beschriftung eines Übergangs: `[zuWarm(temperatur)]{heizung = false;}`
465
+
466
+ ### Kommentare und Notizen
467
+
468
+ - **Kommentar:** Zustände, Übergänge, Funktionsblöcke und das Chart selbst haben in ihren Eigenschaften ein Feld
469
+ **Kommentar**. Eine Sprechblase auf der Zeichenfläche zeigt, dass es einen Kommentar gibt, der Kommentar erscheint
470
+ als Tooltip.
471
+ - **Notizen** (Symbol **Notiz hinzufügen** in der Werkzeugleiste) sind freie Texte auf der Zeichenfläche, wie
472
+ Haftzettel. Lass eine Notiz über einem Zustand los, dann bewegt sie sich mit ihm.
473
+
474
+ Kommentare und Notizen haben keinen Einfluss auf die Ausführung. Sie stehen im generierten Code und in exportierten
475
+ Dateien, und wenn sie sich ändern, wird der Zustand nach einem Neustart trotzdem wiederhergestellt.
476
+
477
+ ### Ein laufendes Chart beobachten
478
+
479
+ Während ein Chart läuft, zeigt der Editor, was es tut:
480
+
481
+ - Die **aktiven Zustände** haben einen grünen Rahmen.
482
+ - **Live-Werte** (Tafel oben links auf der Zeichenfläche, ein- und ausgeschaltet mit dem letzten Symbol der
483
+ Werkzeugleiste): die aktuellen Werte aller Variablen. Sie stehen auch unter jeder Variable im Reiter **Variablen**.
484
+ - Ein **Übergang leuchtet kurz auf**, wenn das Chart ihn nimmt.
485
+ - Der Reiter **Verlauf** listet die Starts des Charts und die genommenen Übergänge, der neueste zuerst: wann, von
486
+ welchem Zustand zu welchem, ausgelöst durch welchen Eingang oder einen Timer (`after`), und welche Zustände danach
487
+ aktiv waren. Ein Klick auf einen Übergang wählt ihn im Chart aus. Der Adapter behält die letzten 200 Ereignisse,
488
+ auch bei geschlossenem Editor und über einen Neustart hinweg. **Verlauf leeren** löscht sie.
489
+
490
+ Live-Werte und Verlauf kommen vom laufenden Chart. Sie beziehen sich also auf das gespeicherte Chart, nicht auf
491
+ ungespeicherte Änderungen im Editor.
492
+
493
+ ### Import und Export
494
+
495
+ - **Chart exportieren** (letztes Symbol in der Werkzeugleiste des Editors) speichert das Chart als JSON-Datei, mit
496
+ ungespeicherten Änderungen.
497
+ - **Alle Charts exportieren** (Symbol über der Chart-Liste) speichert alle gespeicherten Charts in einer Datei.
498
+ - **Charts importieren** (Symbol über der Chart-Liste) fügt die Charts einer solchen Datei als neue Charts hinzu,
499
+ vorhandene Charts werden nicht verändert. Die Datei wird vorher geprüft (Aufbau, IDs, Verweise zwischen Zuständen
500
+ und Übergängen).
501
+
502
+ Importierte Charts sind **angehalten**: Ihre Variablen können auf Datenpunkte eines anderen Systems verweisen. Prüfe sie
503
+ im Reiter **Variablen** und starte das Chart dann mit dem Knopf in der Chart-Liste.
504
+
505
+ ### Vom Adapter angelegte Datenpunkte
506
+
507
+ Für jedes Chart legt der Adapter unter `statechart.<instanz>.charts.<chart>` an:
508
+
509
+ | Datenpunkt | Inhalt |
510
+ |---|---|
511
+ | `activeState` | Name des aktiven Zustands, verschachtelte Zustände als `Eltern.Kind` |
512
+ | `activeStateIds` | IDs aller aktiven Zustände als JSON |
513
+ | `snapshot` | Gespeicherter Zustand des Charts (aktive Zustände, wann sie betreten wurden, lokale Variablen), siehe unten |
514
+ | `values` | Aktuelle Werte aller Variablen als JSON, `{}` wenn das Chart nicht läuft |
515
+ | `history` | Die letzten 200 Ereignisse (Starts und genommene Übergänge) als JSON. Schreiben ohne ack leert ihn |
516
+
517
+ Das Chart selbst ist im Objekt `statechart.<instanz>.charts.<chart>` gespeichert.
518
+
519
+ ### Neustart des Adapters
520
+
521
+ Nach einem Neustart des Adapters oder von ioBroker macht jedes Chart in den Zuständen weiter, die
522
+ vorher aktiv waren, mit seinen lokalen Variablen. Die entry-Aktionen laufen nicht noch einmal, und Timer
523
+ laufen mit ihrer Restzeit weiter. Ein Timer, der abgelaufen ist, während der Adapter gestoppt war, greift
524
+ gleich nach dem Start. Wie nach jedem Start wacht das Chart dann einmal auf, so werden Änderungen der
525
+ Eingänge während des Stopps bemerkt.
526
+
527
+ Im Startzustand beginnt das Chart stattdessen, wenn es zwischendurch geändert wurde (Positionen im
528
+ Editor zählen nicht), nach dem Speichern im Editor, nach dem Anhalten und wenn die Option **Zustand nach
529
+ Neustart wiederherstellen** in den Chart-Eigenschaften ausgeschaltet ist. Variablen, die in
530
+ Funktionsblöcken deklariert sind, werden nicht wiederhergestellt.
531
+
532
+ ### Ausführung und Unterschiede zu anderen Statechart-Werkzeugen
533
+
534
+ - Der Adapter führt die Charts mit [XState](https://stately.ai/docs/xstate) aus.
535
+ - Jede Änderung eines Eingangs weckt das Chart einmal auf, ebenso ein ablaufendes `after(...)` und der Start des
536
+ Charts (auch nach dem Wiederherstellen). Übergänge ohne Event, z.B. ohne Beschriftung, werden bei jedem Aufwecken
537
+ geprüft und greifen deshalb direkt nach dem Start, wenn ihre Bedingung wahr ist.
538
+ - Standardmäßig wird pro Aufwecken nur ein Übergang genommen, ein folgender wartet auf das nächste Aufwecken. Mit der
539
+ Chart-Option **Weiterschalten, bis kein Übergang mehr gilt (Super-Step)** wird das Chart nach einem Übergang sofort
540
+ erneut geprüft, bis kein Übergang mehr gilt, wie bei der Super-Step-Semantik von Stateflow. Das Event eines
541
+ Eingangs zählt nur für den ersten Übergang eines Aufweckens, during-Aktionen können mehrmals laufen. Zum Schutz vor
542
+ Schleifen werden pro Aufwecken höchstens 100 Übergänge genommen, danach wird eine Warnung geloggt.
543
+ - `{aktion}` wird während des Übergangs ausgeführt, also nach `exit` des alten und vor `entry` des
544
+ neuen Zustands. Eine eigene Condition Action und `/aktion` werden (noch) nicht unterstützt.
545
+ - Übergänge werden wie bei Stateflow ausgewählt (außen vor innen, siehe oben), XState führt sie nur aus.
546
+ Reines XState würde die Übergänge der Unterzustände zuerst prüfen.
547
+ - Zeitlogik gibt es nur als `after(n, msec|sec|min)`, gemessen in Echtzeit.
548
+ - Bedingungen und Aktionen sind JavaScript, nicht MATLAB- oder C-Syntax.
549
+
550
+ ### Einschränkungen
551
+
552
+ - Parallele Zustände (AND), History- und Verbindungspunkte werden noch nicht unterstützt.
553
+ - Beim Löschen eines Superstates werden auch seine Unterzustände gelöscht.
554
+ - Ungespeicherte Änderungen gehen verloren, wenn ein anderes Chart ausgewählt wird.
555
+ - Bedingungen und Aktionen laufen in einem eigenen JavaScript-Kontext mit 1 Sekunde Zeitlimit, das
556
+ ist aber **keine Sicherheits-Sandbox**: Wer Charts bearbeiten darf, kann im Adapter beliebigen Code
557
+ ausführen, wie beim JavaScript-Adapter auch.
558
+
559
+ ---
560
+
561
+ ## Development / Entwicklung
562
+
563
+ ```bash
564
+ npm install
565
+ npm run build # build the adapter (src/ to build/) and the admin tab
566
+ npm run dev-server setup # once: local ioBroker test instance in .dev-server/
567
+ npm run dev-server watch # run it, admin on http://127.0.0.1:8081/, rebuilds the adapter on changes
568
+ npm run upload:tab # in a second terminal: upload the tab into the test instance
569
+ ```
570
+
571
+ The adapter and the editor are written in TypeScript. The editor imports the chart model, the validation
572
+ and the code generator directly from `src/lib/`, so they are shared by both.
573
+
574
+ | Script | Description |
575
+ |---|---|
576
+ | `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 |
577
+ | `build` / `watch` | Build the adapter (`src` to `build`) and the tab (`admin/src` to `admin/build`), with type check |
578
+ | `build:ts` / `watch:ts` | Build only the adapter |
579
+ | `build:react` / `watch:react` | Build only the tab |
580
+ | `upload:tab` | Build the tab and write the admin files into the running dev-server instance (works while `dev-server watch` runs) |
581
+ | `test` | Unit tests (`src/**/*.test.ts`) and package tests |
582
+ | `check` | Type check of the adapter and the tab |
583
+ | `lint` | ESLint |
584
+ | `translate` | Translate the texts of the adapter |
585
+ | `release` | Create a new release |
586
+
587
+ After changes of `io-package.json`, run `npm run dev-server upload` and restart `npm run dev-server watch`.
588
+
589
+ **The built adapter (`build`) and tab (`admin/build`) are committed**, because installations from GitHub do
590
+ not run a build. After changes in `src` or `admin/src`, run `npm run build` and commit `build` and
591
+ `admin/build` as well. The workflow job `check-build` fails if they are outdated. The source maps are not
592
+ committed.
593
+
594
+ **Dependabot** proposes updates monthly. Patch updates (and minor updates of development dependencies) are
595
+ merged automatically when the checks pass, which are required for `main`. Dependabot cannot rebuild the
596
+ committed build, so `check-build` does not compare it in Dependabot pull requests. Instead, after every push to
597
+ `main`, it rebuilds `build` and `admin/build` and commits them if they changed. The workflows need the secret
598
+ `AUTO_MERGE_TOKEN`, a fine-grained personal access token for this repository with the permissions
599
+ "Contents" and "Pull requests" (read and write). Major updates of MUI, React and
600
+ `@types/node` are ignored: `@iobroker/adapter-react-v5` requires MUI 6 and React 18, and the adapter supports
601
+ Node.js 22.
602
+
603
+ | Path | Content |
604
+ |---|---|
605
+ | `src/main.ts` | Adapter: loads the charts, subscribes the inputs, restarts a chart when it is saved |
606
+ | `src/lib/chart-model.ts` | Chart model (shared by adapter and editor) and parser of the transition labels |
607
+ | `src/lib/chart-runtime.ts` | Execution of a chart with XState |
608
+ | `src/lib/chart-validation.ts` | Checks of conditions, actions and function blocks |
609
+ | `src/lib/chart-codegen.ts` | Code shown by "Show code" in the editor |
610
+ | `admin/src/` | Editor (React, React Flow, CodeMirror) |
611
+ | `admin/jsonConfig.json` | Instance settings |
612
+
613
+ ## Disclaimer
614
+
615
+ MATLAB and Stateflow are registered trademarks of The MathWorks, Inc. This adapter is not affiliated
616
+ with, endorsed or sponsored by The MathWorks, Inc.
617
+
618
+ ## Changelog
619
+ <!--
620
+ Placeholder for the next version (at the beginning of the line):
621
+ ### **WORK IN PROGRESS**
622
+ -->
623
+ ### 0.2.0 (2026-11-01)
624
+ * (Igge) the adapter and the editor are written in TypeScript now, no changes of the behavior
625
+ * (Igge) live values of the variables in the editor
626
+ * (Igge) taken transitions light up in the editor
627
+ * (Igge) history of the starts and the transitions taken (tab "History", state `charts.<chart>.history`)
628
+ * (Igge) a chart wakes up once right after the start, so transitions without label are taken without waiting for a change of an input
629
+ * (Igge) chart option "super step": take transitions until none is valid anymore
630
+ * (Igge) the arrows of transitions start and end where the states face each other (floating edges), their ends can be dragged along the border of the states or onto another state to move the transition there
631
+ * (Igge) change detection: `hasChanged`, `hasChangedFrom` and `hasChangedTo`, e.g. `hasChangedTo(button, true)` for edges
632
+ * (Igge) suncalc 2: `isDaytime()` is true all day during the midnight sun, `astroTime()` reports events which do not happen on a day (polar regions)
633
+
634
+ ### 0.1.1 (2026-10-25)
635
+ * (Igge) renamed from "state-machine" to "statechart", because npm does not accept the old name (too similar to an existing package)
636
+
637
+ ### 0.1.0 (2026-10-25)
638
+ * (Igge) initial release
639
+ * (Igge) graphical chart editor in the admin side bar: states, transitions, nested states, function blocks, notes
640
+ * (Igge) transition labels in Stateflow syntax event[condition]{action}, timers with after(n, unit)
641
+ * (Igge) execution with Stateflow semantics (outer before inner transitions, inner transitions)
642
+ * (Igge) charts continue in their state after a restart of the adapter
643
+ * (Igge) per input: wake up on updates or changes, filter by ack, debounce
644
+ * (Igge) helper functions: setState, setStateDelayed, sendTo, isTimeBetween, isDaytime, astroTime
645
+ * (Igge) code editor with syntax highlighting, error checks and completion, generated code view
646
+ * (Igge) undo/redo, copy/paste, import/export of charts as JSON
647
+
648
+ ## License
649
+ MIT License
650
+
651
+ Copyright (c) 2026 Igge <mail@igge.org>
652
+
653
+ Permission is hereby granted, free of charge, to any person obtaining a copy
654
+ of this software and associated documentation files (the "Software"), to deal
655
+ in the Software without restriction, including without limitation the rights
656
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
657
+ copies of the Software, and to permit persons to whom the Software is
658
+ furnished to do so, subject to the following conditions:
659
+
660
+ The above copyright notice and this permission notice shall be included in all
661
+ copies or substantial portions of the Software.
662
+
663
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
664
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
665
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
666
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
667
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
668
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
669
+ SOFTWARE.