@impact0815/node-red-contrib-home-connect 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.
Files changed (52) hide show
  1. package/BUILD-INFO.json +23 -0
  2. package/CHANGELOG.md +52 -0
  3. package/LICENSE +21 -0
  4. package/NOTICE +19 -0
  5. package/README.de.md +326 -0
  6. package/README.md +325 -2
  7. package/SECURITY.md +47 -0
  8. package/docs/API-COVERAGE.md +59 -0
  9. package/docs/ARCHITECTURE.md +95 -0
  10. package/docs/DASHBOARD.de.md +121 -0
  11. package/docs/DASHBOARD.md +121 -0
  12. package/docs/FIRST-PUSH.md +42 -0
  13. package/docs/LOCAL-LAN.de.md +150 -0
  14. package/docs/LOCAL-LAN.md +150 -0
  15. package/docs/MESSAGE-API.md +111 -0
  16. package/docs/RELEASE.md +78 -0
  17. package/docs/TROUBLESHOOTING.de.md +230 -0
  18. package/docs/TROUBLESHOOTING.md +230 -0
  19. package/docs/images/README.md +10 -0
  20. package/docs/images/dashboard-details-en.png +0 -0
  21. package/docs/images/dashboard-overview-en.png +0 -0
  22. package/docs/official-api-contract.json +418 -0
  23. package/examples/01-first-steps-device-list.json +148 -0
  24. package/examples/02-events.json +165 -0
  25. package/examples/03-safe-read-write.json +155 -0
  26. package/examples/04-local-read-only.json +214 -0
  27. package/examples/dashboard-dishwasher-washer-oven.json +628 -0
  28. package/examples/dashboard-screenshot-simulated.json +405 -0
  29. package/examples/demo-home-connect-flowfuse-1.34.1-scriptless.json +390 -0
  30. package/lib/client.js +600 -0
  31. package/lib/constants.js +66 -0
  32. package/lib/dashboard-i18n.js +255 -0
  33. package/lib/device-label.js +39 -0
  34. package/lib/errors.js +51 -0
  35. package/lib/health.js +525 -0
  36. package/lib/local-client.js +1014 -0
  37. package/lib/local-crypto.js +122 -0
  38. package/lib/local-diagnostics.js +115 -0
  39. package/lib/local-profile.js +706 -0
  40. package/lib/local-trace.js +103 -0
  41. package/lib/oauth.js +23 -0
  42. package/lib/rate-limiter.js +186 -0
  43. package/lib/simulator.js +290 -0
  44. package/lib/sse.js +58 -0
  45. package/lib/validation.js +194 -0
  46. package/lib/websocket-transport.js +374 -0
  47. package/nodes/home-connect.html +795 -0
  48. package/nodes/home-connect.js +699 -0
  49. package/nodes/locales/de/home-connect.html +19 -0
  50. package/nodes/locales/de/home-connect.json +10 -0
  51. package/nodes/locales/en-US/home-connect.json +10 -0
  52. package/package.json +71 -4
@@ -0,0 +1,390 @@
1
+ [
2
+ {
3
+ "id": "demo-hc-ff1341-fix2-dashboard-flow",
4
+ "type": "tab",
5
+ "label": "Demo Home Connect FlowFuse 1.34.1",
6
+ "disabled": false,
7
+ "info": "Demo only: deterministic simulated appliances, no credentials, real IDs, IPs, profiles or network calls.",
8
+ "wires": []
9
+ },
10
+ {
11
+ "id": "demo-hc-ff1341-fix2-comment",
12
+ "type": "comment",
13
+ "z": "demo-hc-ff1341-fix2-dashboard-flow",
14
+ "name": "Demo FlowFuse 1.34.1 · vollständig simuliert · skriptfreie UI-Templates",
15
+ "info": "Requires @flowfuse/node-red-dashboard and node-red-contrib-home-connect. Health schema: home-connect-health/v1. Device information only; no firmware endpoint is used.",
16
+ "x": 360,
17
+ "y": 40,
18
+ "wires": []
19
+ },
20
+ {
21
+ "id": "demo-hc-ff1341-fix2-ui-base",
22
+ "type": "ui-base",
23
+ "name": "Demo Home Connect FlowFuse 1.34.1",
24
+ "path": "/demo-home-connect-ff1341-fix2",
25
+ "appIcon": "",
26
+ "includeClientData": true,
27
+ "acceptsClientConfig": [
28
+ "ui-notification",
29
+ "ui-control"
30
+ ],
31
+ "showPathInSidebar": false,
32
+ "headerContent": "page",
33
+ "navigationStyle": "default",
34
+ "titleBarStyle": "default",
35
+ "showReconnectNotification": true,
36
+ "notificationDisplayTime": 1,
37
+ "showDisconnectNotification": true,
38
+ "allowInstall": false,
39
+ "wires": []
40
+ },
41
+ {
42
+ "id": "demo-hc-ff1341-fix2-ui-theme",
43
+ "type": "ui-theme",
44
+ "name": "Demo Home Connect FlowFuse 1.34.1",
45
+ "colors": {
46
+ "surface": "#ffffff",
47
+ "primary": "#00629B",
48
+ "bgPage": "#f3f6f9",
49
+ "groupBg": "#ffffff",
50
+ "groupOutline": "#d8e2eb"
51
+ },
52
+ "sizes": {
53
+ "density": "default",
54
+ "pagePadding": "12px",
55
+ "groupGap": "12px",
56
+ "groupBorderRadius": "6px",
57
+ "widgetGap": "12px"
58
+ },
59
+ "wires": []
60
+ },
61
+ {
62
+ "id": "demo-hc-ff1341-fix2-ui-page",
63
+ "type": "ui-page",
64
+ "name": "Demo Gerätedetails",
65
+ "ui": "demo-hc-ff1341-fix2-ui-base",
66
+ "path": "/demo-details",
67
+ "icon": "home",
68
+ "layout": "grid",
69
+ "theme": "demo-hc-ff1341-fix2-ui-theme",
70
+ "breakpoints": [
71
+ {
72
+ "name": "Default",
73
+ "px": "0",
74
+ "cols": "3"
75
+ },
76
+ {
77
+ "name": "Tablet",
78
+ "px": "576",
79
+ "cols": "6"
80
+ },
81
+ {
82
+ "name": "Small Desktop",
83
+ "px": "768",
84
+ "cols": "9"
85
+ },
86
+ {
87
+ "name": "Desktop",
88
+ "px": "1024",
89
+ "cols": "12"
90
+ }
91
+ ],
92
+ "order": 2,
93
+ "className": "",
94
+ "visible": "true",
95
+ "disabled": "false",
96
+ "wires": []
97
+ },
98
+ {
99
+ "id": "demo-hc-ff1341-fix2-group-dw",
100
+ "type": "ui-group",
101
+ "name": "Demo Geschirrspüler",
102
+ "page": "demo-hc-ff1341-fix2-ui-page",
103
+ "width": "4",
104
+ "height": "1",
105
+ "order": 1,
106
+ "showTitle": false,
107
+ "className": "",
108
+ "visible": "true",
109
+ "disabled": "false",
110
+ "wires": []
111
+ },
112
+ {
113
+ "id": "demo-hc-ff1341-fix2-group-wm",
114
+ "type": "ui-group",
115
+ "name": "Demo Waschmaschine",
116
+ "page": "demo-hc-ff1341-fix2-ui-page",
117
+ "width": "4",
118
+ "height": "1",
119
+ "order": 2,
120
+ "showTitle": false,
121
+ "className": "",
122
+ "visible": "true",
123
+ "disabled": "false",
124
+ "wires": []
125
+ },
126
+ {
127
+ "id": "demo-hc-ff1341-fix2-group-ov",
128
+ "type": "ui-group",
129
+ "name": "Demo Backofen",
130
+ "page": "demo-hc-ff1341-fix2-ui-page",
131
+ "width": "4",
132
+ "height": "1",
133
+ "order": 3,
134
+ "showTitle": false,
135
+ "className": "",
136
+ "visible": "true",
137
+ "disabled": "false",
138
+ "wires": []
139
+ },
140
+ {
141
+ "id": "demo-hc-ff1341-fix2-ui-dw",
142
+ "type": "ui-template",
143
+ "z": "demo-hc-ff1341-fix2-dashboard-flow",
144
+ "group": "demo-hc-ff1341-fix2-group-dw",
145
+ "page": "",
146
+ "ui": "",
147
+ "name": "Demo Geschirrspüler",
148
+ "order": 1,
149
+ "width": 0,
150
+ "height": 0,
151
+ "head": "",
152
+ "format": "<template>\n <v-card id=\"demo-hc-dishwasher\" class=\"demo-hc-card\" elevation=\"2\">\n <v-card-title>\n <v-icon class=\"mr-2\">mdi-dishwasher</v-icon>{{ msg?.payload?.deviceTitle || 'Demo Geschirrspüler' }}\n <v-spacer></v-spacer>\n <v-chip color=\"secondary\" size=\"small\" class=\"mr-2\">{{ msg?.payload?.simulatedLabel || 'SIMULIERT' }}</v-chip>\n <v-chip :color=\"msg?.payload?.healthColor || 'grey'\" size=\"small\">{{ msg?.payload?.healthLabel || '—' }}</v-chip>\n </v-card-title>\n <v-card-text>\n <div class=\"demo-hc-device-info\">\n <b>{{ msg?.payload?.deviceTitle || 'Demo Geschirrspüler' }}</b><br>\n {{ msg?.payload?.brand || '—' }} · {{ msg?.payload?.typeLabel || '—' }}<br>\n <span v-if=\"msg?.payload?.vib\">VIB/E-Nummer: {{ msg.payload.vib }}</span><br>\n <span>{{ msg?.payload?.ui?.connection || '—' }}: {{ msg?.payload?.connectionText || '—' }}</span>\n </div>\n <v-alert v-if=\"msg?.payload?.warningText\" :type=\"msg?.payload?.warningType || 'info'\" density=\"compact\" variant=\"tonal\" class=\"mb-2\">\n <b>{{ msg.payload.warningText }}</b>\n </v-alert>\n <div><b>{{ msg?.payload?.ui?.status || '—' }}:</b> {{ msg?.payload?.statusText || '—' }}</div>\n <div><b>{{ msg?.payload?.ui?.door || '—' }}:</b> {{ msg?.payload?.doorText || '—' }}</div>\n <div><b>{{ msg?.payload?.ui?.active || '—' }}:</b> {{ msg?.payload?.activeText || '—' }}</div>\n <div><b>{{ msg?.payload?.ui?.selected || '—' }}:</b> {{ msg?.payload?.selectedText || '—' }}</div>\n <div><b>{{ msg?.payload?.ui?.remaining || '—' }}:</b> {{ msg?.payload?.remainingText || '—' }}</div>\n <div class=\"demo-hc-panel\">\n <div class=\"demo-hc-panel-title\">{{ msg?.payload?.ui?.simulatedValues || '—' }}</div>\n <div><b>{{ msg?.payload?.ui?.remoteControl || '—' }}:</b> {{ msg?.payload?.remoteControlText || '—' }}</div>\n <div><b>{{ msg?.payload?.ui?.remoteStart || '—' }}:</b> {{ msg?.payload?.remoteStartText || '—' }}</div>\n <div><b>{{ msg?.payload?.ui?.localControl || '—' }}:</b> {{ msg?.payload?.localControlText || '—' }}</div>\n <div v-if=\"msg?.payload?.options?.length\" class=\"mt-2\">\n <b>{{ msg?.payload?.ui?.currentOptions || '—' }}:</b>\n <div v-for=\"item in msg.payload.options\" :key=\"item.key\" class=\"demo-hc-option\">\n <span :title=\"item.key\">{{ item.label }}</span>: {{ item.valueText }}<span v-if=\"item.unit\"> {{ item.unit }}</span>\n </div>\n </div>\n <div v-if=\"msg?.payload?.programsText\" class=\"mt-2\"><b>{{ msg?.payload?.ui?.availablePrograms || '—' }}:</b> {{ msg.payload.programsText }}</div>\n </div>\n </v-card-text>\n <v-card-actions>\n <v-btn variant=\"text\" @click=\"send({payload:{action:'refresh'}})\">{{ msg?.payload?.ui?.refresh || 'Refresh' }}</v-btn>\n </v-card-actions>\n </v-card>\n</template>\n<style>\n.demo-hc-card{border-top:4px solid rgb(var(--v-theme-primary));height:100%;scroll-margin-top:72px}\n.demo-hc-device-info{padding:8px 0 12px;color:rgba(var(--v-theme-on-surface),.75)}\n.demo-hc-panel{margin-top:14px;padding:12px;border:1px solid rgba(var(--v-theme-on-surface),.18);border-radius:6px;background:rgba(var(--v-theme-on-surface),.035)}\n.demo-hc-panel-title{font-weight:600;margin-bottom:8px}\n.demo-hc-option{padding-left:10px}\n</style>",
153
+ "storeOutMessages": true,
154
+ "passthru": false,
155
+ "resendOnRefresh": true,
156
+ "templateScope": "local",
157
+ "className": "",
158
+ "x": 670,
159
+ "y": 260,
160
+ "wires": [
161
+ [
162
+ "demo-hc-ff1341-fix2-data"
163
+ ]
164
+ ]
165
+ },
166
+ {
167
+ "id": "demo-hc-ff1341-fix2-ui-wm",
168
+ "type": "ui-template",
169
+ "z": "demo-hc-ff1341-fix2-dashboard-flow",
170
+ "group": "demo-hc-ff1341-fix2-group-wm",
171
+ "page": "",
172
+ "ui": "",
173
+ "name": "Demo Waschmaschine",
174
+ "order": 1,
175
+ "width": 0,
176
+ "height": 0,
177
+ "head": "",
178
+ "format": "<template>\n <v-card id=\"demo-hc-washer\" class=\"demo-hc-card\" elevation=\"2\">\n <v-card-title>\n <v-icon class=\"mr-2\">mdi-washing-machine</v-icon>{{ msg?.payload?.deviceTitle || 'Demo Waschmaschine' }}\n <v-spacer></v-spacer>\n <v-chip color=\"secondary\" size=\"small\" class=\"mr-2\">{{ msg?.payload?.simulatedLabel || 'SIMULIERT' }}</v-chip>\n <v-chip :color=\"msg?.payload?.healthColor || 'grey'\" size=\"small\">{{ msg?.payload?.healthLabel || '—' }}</v-chip>\n </v-card-title>\n <v-card-text>\n <div class=\"demo-hc-device-info\">\n <b>{{ msg?.payload?.deviceTitle || 'Demo Waschmaschine' }}</b><br>\n {{ msg?.payload?.brand || '—' }} · {{ msg?.payload?.typeLabel || '—' }}<br>\n <span v-if=\"msg?.payload?.vib\">VIB/E-Nummer: {{ msg.payload.vib }}</span><br>\n <span>{{ msg?.payload?.ui?.connection || '—' }}: {{ msg?.payload?.connectionText || '—' }}</span>\n </div>\n <v-alert v-if=\"msg?.payload?.warningText\" :type=\"msg?.payload?.warningType || 'info'\" density=\"compact\" variant=\"tonal\" class=\"mb-2\">\n <b>{{ msg.payload.warningText }}</b>\n </v-alert>\n <div><b>{{ msg?.payload?.ui?.status || '—' }}:</b> {{ msg?.payload?.statusText || '—' }}</div>\n <div><b>{{ msg?.payload?.ui?.door || '—' }}:</b> {{ msg?.payload?.doorText || '—' }}</div>\n <div><b>{{ msg?.payload?.ui?.active || '—' }}:</b> {{ msg?.payload?.activeText || '—' }}</div>\n <div><b>{{ msg?.payload?.ui?.selected || '—' }}:</b> {{ msg?.payload?.selectedText || '—' }}</div>\n <div><b>{{ msg?.payload?.ui?.remaining || '—' }}:</b> {{ msg?.payload?.remainingText || '—' }}</div>\n <div class=\"demo-hc-panel\">\n <div class=\"demo-hc-panel-title\">{{ msg?.payload?.ui?.simulatedValues || '—' }}</div>\n <div><b>{{ msg?.payload?.ui?.remoteControl || '—' }}:</b> {{ msg?.payload?.remoteControlText || '—' }}</div>\n <div><b>{{ msg?.payload?.ui?.remoteStart || '—' }}:</b> {{ msg?.payload?.remoteStartText || '—' }}</div>\n <div><b>{{ msg?.payload?.ui?.localControl || '—' }}:</b> {{ msg?.payload?.localControlText || '—' }}</div>\n <div v-if=\"msg?.payload?.options?.length\" class=\"mt-2\">\n <b>{{ msg?.payload?.ui?.currentOptions || '—' }}:</b>\n <div v-for=\"item in msg.payload.options\" :key=\"item.key\" class=\"demo-hc-option\">\n <span :title=\"item.key\">{{ item.label }}</span>: {{ item.valueText }}<span v-if=\"item.unit\"> {{ item.unit }}</span>\n </div>\n </div>\n <div v-if=\"msg?.payload?.programsText\" class=\"mt-2\"><b>{{ msg?.payload?.ui?.availablePrograms || '—' }}:</b> {{ msg.payload.programsText }}</div>\n </div>\n </v-card-text>\n <v-card-actions>\n <v-btn variant=\"text\" @click=\"send({payload:{action:'refresh'}})\">{{ msg?.payload?.ui?.refresh || 'Refresh' }}</v-btn>\n </v-card-actions>\n </v-card>\n</template>\n<style>\n.demo-hc-card{border-top:4px solid rgb(var(--v-theme-primary));height:100%;scroll-margin-top:72px}\n.demo-hc-device-info{padding:8px 0 12px;color:rgba(var(--v-theme-on-surface),.75)}\n.demo-hc-panel{margin-top:14px;padding:12px;border:1px solid rgba(var(--v-theme-on-surface),.18);border-radius:6px;background:rgba(var(--v-theme-on-surface),.035)}\n.demo-hc-panel-title{font-weight:600;margin-bottom:8px}\n.demo-hc-option{padding-left:10px}\n</style>",
179
+ "storeOutMessages": true,
180
+ "passthru": false,
181
+ "resendOnRefresh": true,
182
+ "templateScope": "local",
183
+ "className": "",
184
+ "x": 670,
185
+ "y": 380,
186
+ "wires": [
187
+ [
188
+ "demo-hc-ff1341-fix2-data"
189
+ ]
190
+ ]
191
+ },
192
+ {
193
+ "id": "demo-hc-ff1341-fix2-ui-ov",
194
+ "type": "ui-template",
195
+ "z": "demo-hc-ff1341-fix2-dashboard-flow",
196
+ "group": "demo-hc-ff1341-fix2-group-ov",
197
+ "page": "",
198
+ "ui": "",
199
+ "name": "Demo Backofen",
200
+ "order": 1,
201
+ "width": 0,
202
+ "height": 0,
203
+ "head": "",
204
+ "format": "<template>\n <v-card id=\"demo-hc-oven\" class=\"demo-hc-card\" elevation=\"2\">\n <v-card-title>\n <v-icon class=\"mr-2\">mdi-stove</v-icon>{{ msg?.payload?.deviceTitle || 'Demo Backofen' }}\n <v-spacer></v-spacer>\n <v-chip color=\"secondary\" size=\"small\" class=\"mr-2\">{{ msg?.payload?.simulatedLabel || 'SIMULIERT' }}</v-chip>\n <v-chip :color=\"msg?.payload?.healthColor || 'grey'\" size=\"small\">{{ msg?.payload?.healthLabel || '—' }}</v-chip>\n </v-card-title>\n <v-card-text>\n <div class=\"demo-hc-device-info\">\n <b>{{ msg?.payload?.deviceTitle || 'Demo Backofen' }}</b><br>\n {{ msg?.payload?.brand || '—' }} · {{ msg?.payload?.typeLabel || '—' }}<br>\n <span v-if=\"msg?.payload?.vib\">VIB/E-Nummer: {{ msg.payload.vib }}</span><br>\n <span>{{ msg?.payload?.ui?.connection || '—' }}: {{ msg?.payload?.connectionText || '—' }}</span>\n </div>\n <v-alert v-if=\"msg?.payload?.warningText\" :type=\"msg?.payload?.warningType || 'info'\" density=\"compact\" variant=\"tonal\" class=\"mb-2\">\n <b>{{ msg.payload.warningText }}</b>\n </v-alert>\n <div><b>{{ msg?.payload?.ui?.status || '—' }}:</b> {{ msg?.payload?.statusText || '—' }}</div>\n <div><b>{{ msg?.payload?.ui?.door || '—' }}:</b> {{ msg?.payload?.doorText || '—' }}</div>\n <div><b>{{ msg?.payload?.ui?.active || '—' }}:</b> {{ msg?.payload?.activeText || '—' }}</div>\n <div><b>{{ msg?.payload?.ui?.selected || '—' }}:</b> {{ msg?.payload?.selectedText || '—' }}</div>\n <div><b>{{ msg?.payload?.ui?.remaining || '—' }}:</b> {{ msg?.payload?.remainingText || '—' }}</div>\n <div class=\"demo-hc-panel\">\n <div class=\"demo-hc-panel-title\">{{ msg?.payload?.ui?.simulatedValues || '—' }}</div>\n <div><b>{{ msg?.payload?.ui?.remoteControl || '—' }}:</b> {{ msg?.payload?.remoteControlText || '—' }}</div>\n <div><b>{{ msg?.payload?.ui?.remoteStart || '—' }}:</b> {{ msg?.payload?.remoteStartText || '—' }}</div>\n <div><b>{{ msg?.payload?.ui?.localControl || '—' }}:</b> {{ msg?.payload?.localControlText || '—' }}</div>\n <div v-if=\"msg?.payload?.options?.length\" class=\"mt-2\">\n <b>{{ msg?.payload?.ui?.currentOptions || '—' }}:</b>\n <div v-for=\"item in msg.payload.options\" :key=\"item.key\" class=\"demo-hc-option\">\n <span :title=\"item.key\">{{ item.label }}</span>: {{ item.valueText }}<span v-if=\"item.unit\"> {{ item.unit }}</span>\n </div>\n </div>\n <div v-if=\"msg?.payload?.programsText\" class=\"mt-2\"><b>{{ msg?.payload?.ui?.availablePrograms || '—' }}:</b> {{ msg.payload.programsText }}</div>\n </div>\n </v-card-text>\n <v-card-actions>\n <v-btn variant=\"text\" @click=\"send({payload:{action:'refresh'}})\">{{ msg?.payload?.ui?.refresh || 'Refresh' }}</v-btn>\n </v-card-actions>\n </v-card>\n</template>\n<style>\n.demo-hc-card{border-top:4px solid rgb(var(--v-theme-primary));height:100%;scroll-margin-top:72px}\n.demo-hc-device-info{padding:8px 0 12px;color:rgba(var(--v-theme-on-surface),.75)}\n.demo-hc-panel{margin-top:14px;padding:12px;border:1px solid rgba(var(--v-theme-on-surface),.18);border-radius:6px;background:rgba(var(--v-theme-on-surface),.035)}\n.demo-hc-panel-title{font-weight:600;margin-bottom:8px}\n.demo-hc-option{padding-left:10px}\n</style>",
205
+ "storeOutMessages": true,
206
+ "passthru": false,
207
+ "resendOnRefresh": true,
208
+ "templateScope": "local",
209
+ "className": "",
210
+ "x": 670,
211
+ "y": 500,
212
+ "wires": [
213
+ [
214
+ "demo-hc-ff1341-fix2-data"
215
+ ]
216
+ ]
217
+ },
218
+ {
219
+ "id": "demo-hc-ff1341-fix2-ui-overview-page",
220
+ "type": "ui-page",
221
+ "name": "Demo Übersicht",
222
+ "ui": "demo-hc-ff1341-fix2-ui-base",
223
+ "path": "/demo-overview",
224
+ "icon": "home",
225
+ "layout": "grid",
226
+ "theme": "demo-hc-ff1341-fix2-ui-theme",
227
+ "breakpoints": [
228
+ {
229
+ "name": "Default",
230
+ "px": "0",
231
+ "cols": "3"
232
+ },
233
+ {
234
+ "name": "Tablet",
235
+ "px": "576",
236
+ "cols": "6"
237
+ },
238
+ {
239
+ "name": "Small Desktop",
240
+ "px": "768",
241
+ "cols": "9"
242
+ },
243
+ {
244
+ "name": "Desktop",
245
+ "px": "1024",
246
+ "cols": "12"
247
+ }
248
+ ],
249
+ "order": 1,
250
+ "className": "",
251
+ "visible": "true",
252
+ "disabled": "false",
253
+ "wires": []
254
+ },
255
+ {
256
+ "id": "demo-hc-ff1341-fix2-ui-overview-group",
257
+ "type": "ui-group",
258
+ "name": "Demo Übersicht",
259
+ "page": "demo-hc-ff1341-fix2-ui-overview-page",
260
+ "width": "12",
261
+ "height": "1",
262
+ "order": 1,
263
+ "showTitle": false,
264
+ "className": "",
265
+ "visible": "true",
266
+ "disabled": "false",
267
+ "wires": []
268
+ },
269
+ {
270
+ "id": "demo-hc-ff1341-fix2-ui-overview",
271
+ "type": "ui-template",
272
+ "z": "demo-hc-ff1341-fix2-dashboard-flow",
273
+ "group": "demo-hc-ff1341-fix2-ui-overview-group",
274
+ "page": "",
275
+ "ui": "",
276
+ "name": "Demo Gerätekarten",
277
+ "order": 1,
278
+ "width": 0,
279
+ "height": 0,
280
+ "head": "",
281
+ "format": "<template>\n <div>\n <div class=\"demo-hc-toolbar\">\n <div class=\"demo-hc-heading\">{{ msg?.payload?.ui?.overview || 'Demo Übersicht' }}</div>\n <v-select\n :model-value=\"msg?.payload?.locale || 'de'\"\n :items=\"msg?.payload?.languageItems || [{title:'Deutsch',value:'de'},{title:'English',value:'en'}]\"\n item-title=\"title\" item-value=\"value\"\n :label=\"msg?.payload?.ui?.language || 'Sprache'\"\n density=\"compact\" hide-details class=\"demo-hc-language\"\n @update:model-value=\"send({payload:{action:'set-language',locale:$event}})\">\n </v-select>\n </div>\n <div class=\"demo-hc-grid\">\n <v-card v-for=\"device in msg?.payload?.cards || []\" :key=\"device.key\" class=\"demo-hc-overview-card\" elevation=\"2\" :href=\"device.href\">\n <v-card-title>\n <v-icon class=\"mr-2\">{{ device.icon }}</v-icon>{{ device.title }}\n <v-spacer></v-spacer>\n <v-chip color=\"secondary\" size=\"small\" class=\"mr-2\">{{ msg?.payload?.simulatedLabel || 'SIMULIERT' }}</v-chip>\n <v-chip :color=\"device.healthColor\" size=\"small\">{{ device.healthLabel }}</v-chip>\n </v-card-title>\n <v-card-text>\n <div class=\"demo-hc-meta\">{{ device.brand }} · {{ device.typeLabel }}</div>\n <div><b>{{ msg?.payload?.ui?.status || '—' }}:</b> {{ device.statusText }}</div>\n <div><b>{{ msg?.payload?.ui?.program || '—' }}:</b> {{ device.activeText }}</div>\n <div><b>{{ msg?.payload?.ui?.remaining || '—' }}:</b> {{ device.remainingText }}</div>\n <v-alert v-if=\"device.warningText\" :type=\"device.warningType\" density=\"compact\" variant=\"tonal\" class=\"mt-2\"><b>{{ device.warningText }}</b></v-alert>\n <div class=\"demo-hc-open\">{{ msg?.payload?.ui?.openDetails || 'Details' }} →</div>\n </v-card-text>\n </v-card>\n </div>\n </div>\n</template>\n<style>\n.demo-hc-toolbar{display:flex;align-items:center;justify-content:space-between;gap:16px;margin-bottom:14px}\n.demo-hc-heading{font-size:1.35rem;font-weight:600}.demo-hc-language{max-width:180px}\n.demo-hc-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(250px,1fr));gap:12px}\n.demo-hc-overview-card{border-top:4px solid rgb(var(--v-theme-primary));transition:transform .12s}\n.demo-hc-overview-card:hover,.demo-hc-overview-card:focus{transform:translateY(-2px);outline:2px solid rgb(var(--v-theme-primary))}\n.demo-hc-meta{color:rgba(var(--v-theme-on-surface),.7);margin-bottom:8px}\n.demo-hc-open{margin-top:12px;color:rgb(var(--v-theme-primary));font-weight:600}\n</style>",
282
+ "storeOutMessages": true,
283
+ "passthru": false,
284
+ "resendOnRefresh": true,
285
+ "templateScope": "local",
286
+ "className": "",
287
+ "x": 930,
288
+ "y": 190,
289
+ "wires": [
290
+ [
291
+ "demo-hc-ff1341-fix2-data"
292
+ ]
293
+ ]
294
+ },
295
+ {
296
+ "id": "demo-hc-ff1341-fix2-ui-nav-group",
297
+ "type": "ui-group",
298
+ "name": "Demo Navigation",
299
+ "page": "demo-hc-ff1341-fix2-ui-page",
300
+ "width": "12",
301
+ "height": "1",
302
+ "order": 0,
303
+ "showTitle": false,
304
+ "className": "",
305
+ "visible": "true",
306
+ "disabled": "false",
307
+ "wires": []
308
+ },
309
+ {
310
+ "id": "demo-hc-ff1341-fix2-ui-nav",
311
+ "type": "ui-template",
312
+ "z": "demo-hc-ff1341-fix2-dashboard-flow",
313
+ "group": "demo-hc-ff1341-fix2-ui-nav-group",
314
+ "page": "",
315
+ "ui": "",
316
+ "name": "Demo Zur Übersicht",
317
+ "order": 1,
318
+ "width": 0,
319
+ "height": 0,
320
+ "head": "",
321
+ "format": "<template>\n <div class=\"demo-hc-nav\">\n <v-btn variant=\"text\" prepend-icon=\"mdi-arrow-left\" :href=\"msg?.payload?.overviewPath || '/demo-home-connect-ff1341-fix2/demo-overview'\">\n {{ msg?.payload?.ui?.backToOverview || 'Demo Übersicht' }}\n </v-btn>\n <v-select\n :model-value=\"msg?.payload?.locale || 'de'\"\n :items=\"msg?.payload?.languageItems || [{title:'Deutsch',value:'de'},{title:'English',value:'en'}]\"\n item-title=\"title\" item-value=\"value\"\n :label=\"msg?.payload?.ui?.language || 'Sprache'\"\n density=\"compact\" hide-details class=\"demo-hc-language\"\n @update:model-value=\"send({payload:{action:'set-language',locale:$event}})\">\n </v-select>\n </div>\n</template>\n<style>.demo-hc-nav{display:flex;align-items:center;justify-content:space-between;gap:16px}.demo-hc-language{max-width:180px}</style>",
322
+ "storeOutMessages": true,
323
+ "passthru": false,
324
+ "resendOnRefresh": true,
325
+ "templateScope": "local",
326
+ "className": "",
327
+ "x": 930,
328
+ "y": 240,
329
+ "wires": [
330
+ [
331
+ "demo-hc-ff1341-fix2-data"
332
+ ]
333
+ ]
334
+ },
335
+ {
336
+ "id": "demo-hc-ff1341-fix2-inject",
337
+ "type": "inject",
338
+ "z": "demo-hc-ff1341-fix2-dashboard-flow",
339
+ "name": "Demo Daten laden",
340
+ "props": [
341
+ {
342
+ "p": "payload"
343
+ }
344
+ ],
345
+ "repeat": "",
346
+ "crontab": "",
347
+ "once": true,
348
+ "onceDelay": 0.5,
349
+ "topic": "",
350
+ "payload": "",
351
+ "payloadType": "date",
352
+ "x": 130,
353
+ "y": 120,
354
+ "wires": [
355
+ [
356
+ "demo-hc-ff1341-fix2-data"
357
+ ]
358
+ ]
359
+ },
360
+ {
361
+ "id": "demo-hc-ff1341-fix2-data",
362
+ "type": "function",
363
+ "z": "demo-hc-ff1341-fix2-dashboard-flow",
364
+ "name": "Demo Beispielgeräte erzeugen",
365
+ "func": "let locale = context.get('demoLocale') || 'de';\nconst input = msg && msg.payload && typeof msg.payload === 'object' ? msg.payload : {};\nif (input.action === 'set-language' && (input.locale === 'de' || input.locale === 'en')) {\n locale = input.locale;\n context.set('demoLocale', locale);\n}\nconst text = {\n de: {\n overview:'Demo Übersicht',language:'Sprache',connection:'Verbindung',status:'Status',door:'Tür',active:'Aktiv',selected:'Ausgewählt',remaining:'Restzeit',program:'Programm',\n remoteControl:'Fernsteuerung',remoteStart:'Fernstart',localControl:'Lokale Bedienung',currentOptions:'Aktuelle Optionen',availablePrograms:'Verfügbare Programme',\n simulated:'SIMULIERT',simulatedValues:'Simulierte Beispielwerte · nur Anzeige',refresh:'Aktualisieren',openDetails:'Details öffnen',backToOverview:'Zur Demo Übersicht',\n online:'online',yes:'Ja',no:'Nein',running:'Läuft',ready:'Bereit',closed:'Geschlossen',warning:'Warnung',detergent:'Dosierbehälter fast leer.',\n dishwasher:'Demo Geschirrspüler',washer:'Demo Waschmaschine',oven:'Demo Backofen',dishwasherType:'Geschirrspüler',washerType:'Waschmaschine',ovenType:'Backofen',\n eco50:'Eco 50 °C',cotton:'Baumwolle',hotAir:'Heißluft',intensive70:'Intensiv 70 °C',auto:'Automatik 45–65 °C',glass40:'Glas 40 °C',quick45:'Kurz 45 °C',\n easyCare:'Pflegeleicht',mix:'Mix',delicates:'Fein/Seide',wool:'Wolle',super15:'Super 15',topBottom:'Ober-/Unterhitze',pizza:'Pizzastufe',grill:'Umluftgrill',slowCook:'Sanftgaren',\n progress:'Programmfortschritt',energy:'Energieprognose',water:'Wasserprognose',hygiene:'Hygiene Plus',intensiveZone:'Intensivzone',vario:'varioSpeed Plus',brilliance:'Glanztrocknen',temperature:'Temperatur',spin:'Schleuderdrehzahl',target:'Solltemperatur',fastPreheat:'Schnellaufheizen'\n },\n en: {\n overview:'Demo Overview',language:'Language',connection:'Connection',status:'Status',door:'Door',active:'Active',selected:'Selected',remaining:'Remaining',program:'Program',\n remoteControl:'Remote control',remoteStart:'Remote start',localControl:'Local control',currentOptions:'Current options',availablePrograms:'Available programs',\n simulated:'SIMULATED',simulatedValues:'Simulated example values · display only',refresh:'Refresh',openDetails:'Open details',backToOverview:'Back to Demo Overview',\n online:'online',yes:'Yes',no:'No',running:'Running',ready:'Ready',closed:'Closed',warning:'Warning',detergent:'Detergent dispenser nearly empty.',\n dishwasher:'Demo Dishwasher',washer:'Demo Washer',oven:'Demo Oven',dishwasherType:'Dishwasher',washerType:'Washer',ovenType:'Oven',\n eco50:'Eco 50 °C',cotton:'Cotton',hotAir:'Hot air',intensive70:'Intensive 70 °C',auto:'Auto 45–65 °C',glass40:'Glass 40 °C',quick45:'Quick 45 °C',\n easyCare:'Easy-care',mix:'Mix',delicates:'Delicates/Silk',wool:'Wool',super15:'Super 15',topBottom:'Top/bottom heat',pizza:'Pizza setting',grill:'Hot-air grilling',slowCook:'Slow cook',\n progress:'Program progress',energy:'Energy forecast',water:'Water forecast',hygiene:'Hygiene Plus',intensiveZone:'Intensive zone',vario:'varioSpeed Plus',brilliance:'Brilliance dry',temperature:'Temperature',spin:'Spin speed',target:'Target temperature',fastPreheat:'Fast preheat'\n }\n}[locale];\nconst languageItems = [{title:'Deutsch',value:'de'},{title:'English',value:'en'}];\nconst ui = {overview:text.overview,language:text.language,connection:text.connection,status:text.status,door:text.door,active:text.active,selected:text.selected,remaining:text.remaining,program:text.program,remoteControl:text.remoteControl,remoteStart:text.remoteStart,localControl:text.localControl,currentOptions:text.currentOptions,availablePrograms:text.availablePrograms,simulatedValues:text.simulatedValues,refresh:text.refresh,openDetails:text.openDetails,backToOverview:text.backToOverview};\nconst base = '/demo-home-connect-ff1341-fix2';\nconst common = (key, applianceType, title, typeLabel, healthLabel, healthColor, statusText, activeText, selectedText, remainingText, options, programs, warningText='') => ({\n key, applianceType, locale, languageItems, ui, simulated:true, simulatedLabel:text.simulated, deviceTitle:title, brand:'SIEMENS', typeLabel,\n vib:'DEMO-'+key.toUpperCase()+'-2026', connectionText:text.online, healthLabel, healthColor, statusText, doorText:text.closed,\n activeText, selectedText, remainingText, remoteControlText:text.yes, remoteStartText:key==='washer'?text.no:text.yes, localControlText:text.no,\n options, programsText:programs.join(', '), warningText, warningType:warningText?'warning':'info', href:base+'/demo-details#demo-hc-'+key\n});\nconst opt = (key,label,valueText,unit='') => ({key,label,valueText,unit});\nconst dishwasher = common('dishwasher','Dishwasher',text.dishwasher,text.dishwasherType,text.running,'info',text.running,text.eco50,text.eco50,'62 min',[\n opt('BSH.Common.Option.ProgramProgress',text.progress,'43','%'),opt('BSH.Common.Option.EnergyForecast',text.energy,'62','%'),opt('BSH.Common.Option.WaterForecast',text.water,'48','%'),\n opt('Dishcare.Dishwasher.Option.HygienePlus',text.hygiene,text.yes),opt('Dishcare.Dishwasher.Option.IntensivZone',text.intensiveZone,text.no),opt('Dishcare.Dishwasher.Option.VarioSpeedPlus',text.vario,text.yes),opt('Dishcare.Dishwasher.Option.BrillianceDry',text.brilliance,text.yes)\n],[text.intensive70,text.auto,text.eco50,text.glass40,text.quick45]);\nconst washer = common('washer','Washer',text.washer,text.washerType,text.warning,'warning',text.ready,'—',text.cotton,'—',[\n opt('LaundryCare.Washer.Option.Temperature',text.temperature,'40','°C'),opt('LaundryCare.Washer.Option.SpinSpeed',text.spin,'1400','rpm')\n],[text.cotton,text.easyCare,text.mix,text.delicates,text.wool,text.super15],text.detergent);\nconst oven = common('oven','Oven',text.oven,text.ovenType,text.running,'info',text.running,text.hotAir,text.hotAir,'28 min',[\n opt('Cooking.Oven.Option.SetpointTemperature',text.target,'180','°C'),opt('Cooking.Oven.Option.FastPreHeat',text.fastPreheat,text.no),opt('BSH.Common.Option.ProgramProgress',text.progress,'68','%')\n],[text.hotAir,text.topBottom,text.pizza,text.grill,text.slowCook]);\nconst detail = device => ({topic:'demo-'+device.key,payload:device});\nconst overview = {topic:'demo-overview',payload:{locale,languageItems,ui,simulatedLabel:text.simulated,overviewPath:base+'/demo-overview',cards:[dishwasher,washer,oven]}};\nreturn [detail(dishwasher), detail(washer), detail(oven), overview];",
366
+ "outputs": 4,
367
+ "timeout": 0,
368
+ "noerr": 0,
369
+ "initialize": "",
370
+ "finalize": "",
371
+ "libs": [],
372
+ "x": 360,
373
+ "y": 120,
374
+ "wires": [
375
+ [
376
+ "demo-hc-ff1341-fix2-ui-dw"
377
+ ],
378
+ [
379
+ "demo-hc-ff1341-fix2-ui-wm"
380
+ ],
381
+ [
382
+ "demo-hc-ff1341-fix2-ui-ov"
383
+ ],
384
+ [
385
+ "demo-hc-ff1341-fix2-ui-overview",
386
+ "demo-hc-ff1341-fix2-ui-nav"
387
+ ]
388
+ ]
389
+ }
390
+ ]