node-red-contrib-event-calc 3.3.6 → 3.3.15

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,11 +1,13 @@
1
1
  # node-red-contrib-event-calc
2
2
 
3
- Node-RED nodes for event caching and streaming calculations with a local pub/sub event hub.
3
+ Node-RED nodes for event caching, streaming calculations, alarm management, and event framing.
4
4
 
5
5
  ## Overview
6
6
 
7
7
  This package provides a local in-memory event hub with topic-based publish/subscribe and latest-value caching for reactive data flows within Node-RED. Stream data from MQTT, OPC-UA, or any source, then perform calculations that trigger automatically when values update.
8
8
 
9
+ Also includes ISA-88 batch structure tracking, ISA-18.2 alarm lifecycle management, and simple event recording.
10
+
9
11
  ## Architecture
10
12
 
11
13
  ```
@@ -34,14 +36,16 @@ Or install directly from the Node-RED palette manager.
34
36
 
35
37
  ## Nodes
36
38
 
37
- ### event-cache (Config Node)
39
+ ### Core Nodes
40
+
41
+ #### event-cache (Config Node)
38
42
 
39
43
  Central cache that stores topic values and manages subscriptions. Configure:
40
44
 
41
45
  - **Max Entries**: Maximum topics to cache (default: 10000). Oldest entries removed when exceeded.
42
46
  - **TTL**: Time-to-live in milliseconds. Set to 0 for no expiry.
43
47
 
44
- ### event-in
48
+ #### event-in
45
49
 
46
50
  Receives messages from any upstream node and pushes values to the cache.
47
51
 
@@ -51,7 +55,7 @@ Receives messages from any upstream node and pushes values to the cache.
51
55
 
52
56
  The original message passes through, allowing insertion into existing flows.
53
57
 
54
- ### event-topic
58
+ #### event-topic
55
59
 
56
60
  Subscribes to a topic and outputs when that topic updates.
57
61
 
@@ -66,7 +70,7 @@ Subscribes to a topic and outputs when that topic updates.
66
70
  - `msg.topic`: Change subscription topic
67
71
  - `msg.payload = 'refresh'`: Output current cached value
68
72
 
69
- ### event-calc
73
+ #### event-calc
70
74
 
71
75
  Subscribes to multiple topics and evaluates an expression when values update.
72
76
 
@@ -92,71 +96,113 @@ Subscribes to multiple topics and evaluates an expression when values update.
92
96
  }
93
97
  ```
94
98
 
95
- ### event-json
99
+ ### Event Frame Nodes
96
100
 
97
- Bidirectional JSON envelope converter for MQTT messaging.
101
+ #### event-frame (ISA-88 Batch Structure)
98
102
 
99
- **Behavior:**
100
- - **Unwrap**: If payload is `{value, topic?, timestamp?}`, extracts to msg properties
101
- - **Wrap**: If payload is any other value, wraps as `{timestamp, topic, value}`
103
+ Creates ISA-88 procedural model records with hierarchical parent-child linking.
102
104
 
103
- **Usage:**
104
- ```
105
- [MQTT in] [event-json] [event-in] (unwrap JSON from broker)
106
- [event-topic] [event-json] [MQTT out] (wrap for broker)
105
+ **ISA-88 Levels:**
106
+ - **Procedure** — top-level batch recipe
107
+ - **Unit Procedure** sequence within a unit
108
+ - **Operation** major processing step
109
+ - **Phase** — lowest-level action
110
+
111
+ **Features:**
112
+ - Trigger-based: truthy starts, falsy ends
113
+ - Auto-generated UUIDs
114
+ - Parent-child linking via `parentLevel` config or `msg.frame_id` chaining
115
+ - Cascade end: ending a parent auto-ends all children recursively
116
+ - Shared global context tracker for cross-node coordination
117
+
118
+ **Output Record:**
119
+ ```json
120
+ {
121
+ "id": "auto-generated UUID",
122
+ "starttime": "2024-01-15T10:30:00.000Z",
123
+ "endtime": "9999-12-31T23:59:59.000Z",
124
+ "name": "Mixing",
125
+ "parent_id": "parent-uuid-or-empty",
126
+ "level": "operation",
127
+ "state": "running",
128
+ "batch_id": "BATCH-001",
129
+ "unit": "Reactor-1",
130
+ "metadata": ""
131
+ }
107
132
  ```
108
133
 
109
- ### event-simulator
134
+ #### simple-frame (Simple Event Recorder)
110
135
 
111
- Generates simulated data for testing. Supports sine waves, random values, and ramps.
136
+ A simplified event frame for tracking any event with start/end time — no hierarchy, no cascade.
112
137
 
113
- ### event-chart
138
+ **Properties:**
139
+ - **Event Type**: Category label (str/msg/flow/global/env)
140
+ - **Event Name**: Descriptive name (str/msg/flow/global/env)
141
+ - **Metadata Field**: Optional msg property for extra data
142
+ - **Two outputs**: start and end
114
143
 
115
- Real-time charting node for visualizing cached event data.
144
+ **Output Record:**
145
+ ```json
146
+ {
147
+ "id": "auto-generated UUID",
148
+ "starttime": "2024-01-15T10:30:00.000Z",
149
+ "endtime": "9999-12-31T23:59:59.000Z",
150
+ "type": "maintenance",
151
+ "name": "Oil Change",
152
+ "state": "running",
153
+ "metadata": ""
154
+ }
155
+ ```
116
156
 
117
- ## Examples
157
+ ### Alarm Management
118
158
 
119
- ### Average Temperature
159
+ #### event-alarm (ISA-18.2 Alarm Lifecycle)
120
160
 
121
- ```
122
- [inject: room1/temp] → [event-in] → [cache]
123
- [inject: room2/temp] → [event-in] → [cache]
124
-
125
- [event-calc] → [debug]
126
- inputs: a = sensors/room1/temp
127
- b = sensors/room2/temp
128
- expression: (a + b) / 2
129
- trigger: all
130
- ```
161
+ Tracks alarms through the full ISA-18.2 lifecycle with four outputs.
131
162
 
132
- ### Time-based Calculations (External Trigger)
163
+ **Alarm States:**
164
+ - **UNACK_ALM** — Active + Unacknowledged (just raised)
165
+ - **ACK_ALM** — Active + Acknowledged (operator acked, condition still true)
166
+ - **UNACK_RTN** — Inactive + Unacknowledged (condition cleared, not yet acked)
167
+ - **NORM** — Normal (fully resolved)
133
168
 
169
+ **Lifecycle Paths:**
134
170
  ```
135
- [inject: every 1 min][event-calc (external trigger)] [MQTT out]
136
- inputs: a = sensors/power
137
- b = sensors/voltage
138
- expression: a * b
171
+ Path A: NORM UNACK_ALMACK_ALM → NORM (ack first, then clear)
172
+ Path B: NORM UNACK_ALM → UNACK_RTN → NORM (clear first, then ack)
139
173
  ```
140
174
 
141
- ### MQTT Round-trip with JSON Envelope
175
+ **Properties:**
176
+ - **Condition ID/Name**: Alarm identifier and description
177
+ - **Input Mappings**: Map variable names to cache topics (like event-calc)
178
+ - **Condition**: Expression that evaluates to true/false (e.g. `active == true`)
179
+ - **Severity**: 0-1000 scale
180
+ - **4 Outputs**: raised, acknowledged, cleared, resolved
142
181
 
143
- ```
144
- [MQTT in] [event-json] [event-in] [cache]
182
+ **Actions (via msg.payload):**
183
+ - `{ action: "ack", source: "topic" }` — Acknowledge specific alarm
184
+ - `{ action: "ack_all" }` — Acknowledge all active alarms
185
+ - `{ action: "list" }` — List all active alarms
145
186
 
146
- [event-calc] [event-json] → [MQTT out]
147
- ```
187
+ ### Utility Nodes
148
188
 
149
- ### Calculate Power (Voltage × Current)
189
+ #### event-flatten
150
190
 
151
- ```
152
- [event-calc]
153
- inputs: v = power/voltage
154
- i = power/current
155
- expression: v * i
156
- topic: power/watts
157
- ```
191
+ Flattens nested objects into individual topic/value pairs for the cache.
192
+
193
+ #### event-preview
194
+
195
+ Preview cached values directly in the Node-RED editor.
158
196
 
159
- ## Built-in Functions
197
+ #### event-simulator
198
+
199
+ Generates simulated data for testing. Supports sine waves, random values, and ramps.
200
+
201
+ #### event-chart
202
+
203
+ Real-time charting node for visualizing cached event data.
204
+
205
+ ## Built-in Functions (event-calc)
160
206
 
161
207
  ### Math
162
208
  | Function | Description |
@@ -198,8 +244,6 @@ Real-time charting node for visualizing cached event data.
198
244
  | `hasChanged('varName')` | `true` if value differs from previous (false on first message) |
199
245
  | `timeSinceLastChange('varName')` | Milliseconds since the value last changed |
200
246
 
201
- The cache automatically tracks the previous value for every topic. On the first message, `prev()` returns the same value as the current (so `hasChanged()` returns `false` and delta is `0`).
202
-
203
247
  ### Date/Time
204
248
  | Function | Description |
205
249
  |----------|-------------|
@@ -226,13 +270,9 @@ The cache automatically tracks the previous value for every topic. On the first
226
270
  | `clamp(a, 0, 100)` | Constrain 0-100 |
227
271
  | `map(a, 0, 1023, 0, 100)` | Scale ADC to % |
228
272
  | `ifelse(a > b, 'high', 'low')` | Conditional |
229
- | `pctChange(a, b)` | % change from b to a |
230
273
  | `hasChanged('temp')` | Did temperature just change? |
231
274
  | `temp - prev('temp')` | Delta from previous value |
232
- | `round(timeSinceLastChange('temp') / 1000, 1)` | Seconds since last change |
233
275
  | `isWeekday() && hoursBetween(8, 18)` | During business hours? |
234
- | `hoursBetween(22, 6)` | Night shift (wraps midnight) |
235
- | `ifelse(isWeekend(), temp * 0.8, temp)` | Reduce setpoint on weekends |
236
276
 
237
277
  ## API (for custom nodes)
238
278
 
@@ -248,10 +288,6 @@ cache.setValue('topic/path', 42, { source: 'sensor' });
248
288
  const entry = cache.getValue('topic/path');
249
289
  // { value: 42, ts: 1704000000000, metadata: { source: 'sensor' }, previous: { value: 40, ts: ..., metadata: ... } }
250
290
 
251
- // Get previous value
252
- const prev = cache.getPrevious('topic/path');
253
- // { value: 40, ts: 1703999990000, metadata: { source: 'sensor' } }
254
-
255
291
  // Subscribe to updates
256
292
  const subId = cache.subscribe('sensors/room1/temp', (topic, entry) => {
257
293
  console.log(`${topic} = ${entry.value}`);
@@ -270,9 +306,11 @@ cache.clear();
270
306
  ## HTTP Admin Endpoints
271
307
 
272
308
  ```
273
- GET /event-cache/:id/stats - Cache statistics
274
- GET /event-cache/:id/topics - List all topics
275
- POST /event-cache/:id/clear - Clear cache
309
+ GET /event-cache/:id/stats - Cache statistics
310
+ GET /event-cache/:id/topics - List all topics
311
+ POST /event-cache/:id/clear - Clear cache
312
+ GET /event-frame/tracker - Current batch tracker state
313
+ GET /event-alarm/:id/alarms - Active alarms for a node
276
314
  ```
277
315
 
278
316
  ## License
@@ -0,0 +1,288 @@
1
+ [
2
+ {
3
+ "id": "alarm-sim-flow",
4
+ "type": "tab",
5
+ "label": "Alarm Lifecycle Simulation",
6
+ "disabled": false,
7
+ "info": "Self-running alarm lifecycle simulation.\n\nEvery few seconds a random alarm is raised. After a random delay it gets\nacknowledged, and after another random delay the condition clears.\nThe event-alarm nodes track the full ISA-18.2 lifecycle.\n\nBoth lifecycle paths are exercised:\n Path A: Raised > Acked > Cleared (Resolved)\n Path B: Raised > Cleared > Acked (Resolved)"
8
+ },
9
+ {
10
+ "id": "sim-cache",
11
+ "type": "event-cache",
12
+ "name": "Sim Cache",
13
+ "maxEntries": "10000",
14
+ "ttl": "0"
15
+ },
16
+ {
17
+ "id": "comment-sim-title",
18
+ "type": "comment",
19
+ "z": "alarm-sim-flow",
20
+ "name": "Alarm Lifecycle Simulation (self-running)",
21
+ "info": "Raises random alarms every N seconds, then auto-acks and auto-clears after random delays.\nWatch the debug sidebar to see the full lifecycle play out.",
22
+ "x": 230,
23
+ "y": 40,
24
+ "wires": []
25
+ },
26
+ {
27
+ "id": "sim-trigger",
28
+ "type": "inject",
29
+ "z": "alarm-sim-flow",
30
+ "name": "Every 5s: raise random alarm",
31
+ "props": [
32
+ { "p": "payload", "v": "", "vt": "date" }
33
+ ],
34
+ "repeat": "5",
35
+ "crontab": "",
36
+ "once": true,
37
+ "onceDelay": "2",
38
+ "x": 200,
39
+ "y": 120,
40
+ "wires": [["sim-raise-alarm"]]
41
+ },
42
+ {
43
+ "id": "sim-raise-alarm",
44
+ "type": "function",
45
+ "z": "alarm-sim-flow",
46
+ "name": "Raise Random Alarm",
47
+ "func": "const alarms = [\n { topic: 'sim/plant/PumpTrip_P1A/active', name: 'PumpTrip_P1A' },\n { topic: 'sim/plant/BreakerFault_MCC3/active', name: 'BreakerFault_MCC3' },\n { topic: 'sim/plant/HighLevel_Thickener/active', name: 'HighLevel_Thickener' },\n { topic: 'sim/plant/VibrationHigh_Screen2/active', name: 'VibrationHigh_Screen2' },\n { topic: 'sim/plant/EStop_Conveyor7/active', name: 'EStop_Conveyor7' },\n { topic: 'sim/plant/MagnetiteLevel_Low/active', name: 'MagnetiteLevel_Low' }\n];\n\n// Pick a random alarm\nconst alarm = alarms[Math.floor(Math.random() * alarms.length)];\n\n// Activate it\nconst activateMsg = {\n topic: alarm.topic,\n payload: true\n};\n\n// Schedule ack after 3-10s random delay\nconst ackDelay = 3000 + Math.floor(Math.random() * 7000);\n// Schedule clear after 5-15s random delay\nconst clearDelay = 5000 + Math.floor(Math.random() * 10000);\n\n// Randomly choose lifecycle path:\n// Path A (60%): ack first, then clear\n// Path B (40%): clear first, then ack\nconst ackFirst = Math.random() < 0.6;\n\nconst ackMsg = {\n payload: { action: 'ack', source: alarm.topic },\n _alarm: alarm.name,\n _delay: ackFirst ? ackDelay : clearDelay + ackDelay\n};\n\nconst clearMsg = {\n topic: alarm.topic,\n payload: false,\n _alarm: alarm.name,\n _delay: ackFirst ? clearDelay + ackDelay : clearDelay\n};\n\n// Store scheduled actions in context for the delay nodes\nnode.status({ text: `${alarm.name} (${ackFirst ? 'ack-first' : 'clear-first'})` });\n\n// Output 1: activate now\n// Output 2: ack (with delay metadata)\n// Output 3: clear (with delay metadata)\nreturn [activateMsg, ackMsg, clearMsg];",
48
+ "outputs": 3,
49
+ "outputLabels": ["activate", "ack (delayed)", "clear (delayed)"],
50
+ "x": 480,
51
+ "y": 120,
52
+ "wires": [["sim-event-in"], ["sim-delay-ack"], ["sim-delay-clear"]]
53
+ },
54
+ {
55
+ "id": "sim-delay-ack",
56
+ "type": "function",
57
+ "z": "alarm-sim-flow",
58
+ "name": "Random Delay (ack)",
59
+ "func": "const delay = msg._delay || 5000;\nconst alarmName = msg._alarm || 'unknown';\n\nreturn new Promise(resolve => {\n const timerId = setTimeout(() => {\n node.status({ text: `ack ${alarmName} after ${(delay/1000).toFixed(1)}s` });\n delete msg._delay;\n delete msg._alarm;\n resolve(msg);\n }, delay);\n \n // Clean up on node close\n node.on('close', () => clearTimeout(timerId));\n});",
60
+ "outputs": 1,
61
+ "x": 480,
62
+ "y": 200,
63
+ "wires": [["sim-ack-router"]]
64
+ },
65
+ {
66
+ "id": "sim-delay-clear",
67
+ "type": "function",
68
+ "z": "alarm-sim-flow",
69
+ "name": "Random Delay (clear)",
70
+ "func": "const delay = msg._delay || 8000;\nconst alarmName = msg._alarm || 'unknown';\n\nreturn new Promise(resolve => {\n const timerId = setTimeout(() => {\n node.status({ text: `clear ${alarmName} after ${(delay/1000).toFixed(1)}s` });\n delete msg._delay;\n delete msg._alarm;\n resolve(msg);\n }, delay);\n \n // Clean up on node close\n node.on('close', () => clearTimeout(timerId));\n});",
71
+ "outputs": 1,
72
+ "x": 490,
73
+ "y": 280,
74
+ "wires": [["sim-event-in"]]
75
+ },
76
+ {
77
+ "id": "sim-event-in",
78
+ "type": "event-in",
79
+ "z": "alarm-sim-flow",
80
+ "name": "Push to Cache",
81
+ "cache": "sim-cache",
82
+ "topicSource": "msg",
83
+ "topicPattern": "",
84
+ "x": 710,
85
+ "y": 120,
86
+ "wires": [[]]
87
+ },
88
+ {
89
+ "id": "sim-ack-router",
90
+ "type": "function",
91
+ "z": "alarm-sim-flow",
92
+ "name": "Route ACK to alarm node",
93
+ "func": "// Route the ack to the correct alarm node based on source topic\nconst source = msg.payload.source;\nconst routes = {\n 'sim/plant/PumpTrip_P1A/active': 0,\n 'sim/plant/BreakerFault_MCC3/active': 1,\n 'sim/plant/HighLevel_Thickener/active': 2,\n 'sim/plant/VibrationHigh_Screen2/active': 3,\n 'sim/plant/EStop_Conveyor7/active': 4,\n 'sim/plant/MagnetiteLevel_Low/active': 5\n};\n\nconst outputs = [null, null, null, null, null, null];\nconst idx = routes[source];\nif (idx !== undefined) {\n outputs[idx] = msg;\n}\nreturn outputs;",
94
+ "outputs": 6,
95
+ "outputLabels": ["Pump", "Breaker", "Thickener", "Vibration", "EStop", "Magnetite"],
96
+ "x": 720,
97
+ "y": 200,
98
+ "wires": [["sim-alarm-pump"], ["sim-alarm-breaker"], ["sim-alarm-thickener"], ["sim-alarm-vibration"], ["sim-alarm-estop"], ["sim-alarm-magnetite"]]
99
+ },
100
+ {
101
+ "id": "comment-alarm-nodes",
102
+ "type": "comment",
103
+ "z": "alarm-sim-flow",
104
+ "name": "--- Event Alarm Nodes ---",
105
+ "info": "",
106
+ "x": 940,
107
+ "y": 40,
108
+ "wires": []
109
+ },
110
+ {
111
+ "id": "sim-alarm-pump",
112
+ "type": "event-alarm",
113
+ "z": "alarm-sim-flow",
114
+ "name": "Pump P1A Trip",
115
+ "cache": "sim-cache",
116
+ "conditionId": "PumpTrip_P1A",
117
+ "conditionName": "Heavy Media Pump P1A Trip",
118
+ "inputMappings": [
119
+ { "name": "active", "topic": "sim/plant/PumpTrip_P1A/active" }
120
+ ],
121
+ "condition": "active == true",
122
+ "severity": "800",
123
+ "outputTopic": "alarm/PumpTrip_P1A",
124
+ "x": 960,
125
+ "y": 100,
126
+ "wires": [["debug-sim-raised"], ["debug-sim-acked"], ["debug-sim-cleared"], ["debug-sim-resolved"]]
127
+ },
128
+ {
129
+ "id": "sim-alarm-breaker",
130
+ "type": "event-alarm",
131
+ "z": "alarm-sim-flow",
132
+ "name": "Breaker MCC3 Fault",
133
+ "cache": "sim-cache",
134
+ "conditionId": "BreakerFault_MCC3",
135
+ "conditionName": "MCC3 Main Breaker Fault",
136
+ "inputMappings": [
137
+ { "name": "active", "topic": "sim/plant/BreakerFault_MCC3/active" }
138
+ ],
139
+ "condition": "active == true",
140
+ "severity": "900",
141
+ "outputTopic": "alarm/BreakerFault_MCC3",
142
+ "x": 970,
143
+ "y": 180,
144
+ "wires": [["debug-sim-raised"], ["debug-sim-acked"], ["debug-sim-cleared"], ["debug-sim-resolved"]]
145
+ },
146
+ {
147
+ "id": "sim-alarm-thickener",
148
+ "type": "event-alarm",
149
+ "z": "alarm-sim-flow",
150
+ "name": "Thickener Level High",
151
+ "cache": "sim-cache",
152
+ "conditionId": "HighLevel_Thickener",
153
+ "conditionName": "Thickener Overflow Level High",
154
+ "inputMappings": [
155
+ { "name": "active", "topic": "sim/plant/HighLevel_Thickener/active" }
156
+ ],
157
+ "condition": "active == true",
158
+ "severity": "600",
159
+ "outputTopic": "alarm/HighLevel_Thickener",
160
+ "x": 970,
161
+ "y": 260,
162
+ "wires": [["debug-sim-raised"], ["debug-sim-acked"], ["debug-sim-cleared"], ["debug-sim-resolved"]]
163
+ },
164
+ {
165
+ "id": "sim-alarm-vibration",
166
+ "type": "event-alarm",
167
+ "z": "alarm-sim-flow",
168
+ "name": "Screen 2 Vibration",
169
+ "cache": "sim-cache",
170
+ "conditionId": "VibrationHigh_Screen2",
171
+ "conditionName": "Screen 2 Vibration Alarm High",
172
+ "inputMappings": [
173
+ { "name": "active", "topic": "sim/plant/VibrationHigh_Screen2/active" }
174
+ ],
175
+ "condition": "active == true",
176
+ "severity": "500",
177
+ "outputTopic": "alarm/VibrationHigh_Screen2",
178
+ "x": 970,
179
+ "y": 340,
180
+ "wires": [["debug-sim-raised"], ["debug-sim-acked"], ["debug-sim-cleared"], ["debug-sim-resolved"]]
181
+ },
182
+ {
183
+ "id": "sim-alarm-estop",
184
+ "type": "event-alarm",
185
+ "z": "alarm-sim-flow",
186
+ "name": "Conveyor 7 EStop",
187
+ "cache": "sim-cache",
188
+ "conditionId": "EStop_Conveyor7",
189
+ "conditionName": "Conveyor 7 Emergency Stop",
190
+ "inputMappings": [
191
+ { "name": "active", "topic": "sim/plant/EStop_Conveyor7/active" }
192
+ ],
193
+ "condition": "active == true",
194
+ "severity": "1000",
195
+ "outputTopic": "alarm/EStop_Conveyor7",
196
+ "x": 970,
197
+ "y": 420,
198
+ "wires": [["debug-sim-raised"], ["debug-sim-acked"], ["debug-sim-cleared"], ["debug-sim-resolved"]]
199
+ },
200
+ {
201
+ "id": "sim-alarm-magnetite",
202
+ "type": "event-alarm",
203
+ "z": "alarm-sim-flow",
204
+ "name": "Magnetite Level Low",
205
+ "cache": "sim-cache",
206
+ "conditionId": "MagnetiteLevel_Low",
207
+ "conditionName": "Magnetite Storage Tank Level Low",
208
+ "inputMappings": [
209
+ { "name": "active", "topic": "sim/plant/MagnetiteLevel_Low/active" }
210
+ ],
211
+ "condition": "active == true",
212
+ "severity": "400",
213
+ "outputTopic": "alarm/MagnetiteLevel_Low",
214
+ "x": 970,
215
+ "y": 500,
216
+ "wires": [["debug-sim-raised"], ["debug-sim-acked"], ["debug-sim-cleared"], ["debug-sim-resolved"]]
217
+ },
218
+ {
219
+ "id": "comment-debug-outputs",
220
+ "type": "comment",
221
+ "z": "alarm-sim-flow",
222
+ "name": "--- Lifecycle Debug Outputs ---",
223
+ "info": "",
224
+ "x": 1270,
225
+ "y": 40,
226
+ "wires": []
227
+ },
228
+ {
229
+ "id": "debug-sim-raised",
230
+ "type": "debug",
231
+ "z": "alarm-sim-flow",
232
+ "name": "RAISED",
233
+ "active": true,
234
+ "tosidebar": true,
235
+ "console": false,
236
+ "tostatus": true,
237
+ "statusVal": "payload.condition_name",
238
+ "statusType": "msg",
239
+ "x": 1240,
240
+ "y": 100,
241
+ "wires": []
242
+ },
243
+ {
244
+ "id": "debug-sim-acked",
245
+ "type": "debug",
246
+ "z": "alarm-sim-flow",
247
+ "name": "ACKNOWLEDGED",
248
+ "active": true,
249
+ "tosidebar": true,
250
+ "console": false,
251
+ "tostatus": true,
252
+ "statusVal": "payload.condition_name",
253
+ "statusType": "msg",
254
+ "x": 1260,
255
+ "y": 180,
256
+ "wires": []
257
+ },
258
+ {
259
+ "id": "debug-sim-cleared",
260
+ "type": "debug",
261
+ "z": "alarm-sim-flow",
262
+ "name": "CLEARED",
263
+ "active": true,
264
+ "tosidebar": true,
265
+ "console": false,
266
+ "tostatus": true,
267
+ "statusVal": "payload.condition_name",
268
+ "statusType": "msg",
269
+ "x": 1250,
270
+ "y": 260,
271
+ "wires": []
272
+ },
273
+ {
274
+ "id": "debug-sim-resolved",
275
+ "type": "debug",
276
+ "z": "alarm-sim-flow",
277
+ "name": "RESOLVED",
278
+ "active": true,
279
+ "tosidebar": true,
280
+ "console": false,
281
+ "tostatus": true,
282
+ "statusVal": "payload.condition_name",
283
+ "statusType": "msg",
284
+ "x": 1250,
285
+ "y": 340,
286
+ "wires": []
287
+ }
288
+ ]