iobroker.poolcontrol 1.3.2 → 1.3.5

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.
@@ -0,0 +1,802 @@
1
+ 'use strict';
2
+
3
+ const { I18n } = require('@iobroker/adapter-core');
4
+
5
+ /**
6
+ * solarInsightsHelper
7
+ * - Analysiert verfügbare und verwendete Sensoren für analytics.insights.solar
8
+ * - Prüft, ob Solar heute überhaupt gelaufen ist
9
+ * - Schreibt Transparenz-, Referenz-, Leistungs- und Tagesertrags-States
10
+ * - Arbeitet eventbasiert mit debounce per adapter.setTimeout
11
+ */
12
+
13
+ const solarInsightsHelper = {
14
+ adapter: null,
15
+ checkTimer: null,
16
+ resetTimer: null,
17
+ lastCheckTimestamp: null,
18
+
19
+ init(adapter) {
20
+ this.adapter = adapter;
21
+
22
+ void this._subscribeStates();
23
+ this._scheduleDailyReset();
24
+ this._scheduleCheck(0);
25
+
26
+ this.adapter.log.debug('[solarInsightsHelper] Initialized (event-based precheck)');
27
+ },
28
+
29
+ onStateChange(id, state) {
30
+ if (!state || state.ack !== true) {
31
+ return;
32
+ }
33
+
34
+ if (!this._isRelevantState(id)) {
35
+ return;
36
+ }
37
+
38
+ // Tages-Latch sofort setzen, wenn Solar aktiv wird
39
+ if ((id === 'solar.active' || id === 'solar.extended.active') && state.val === true) {
40
+ void this.adapter.setStateChangedAsync('analytics.insights.solar.results.solar_ran_today', {
41
+ val: true,
42
+ ack: true,
43
+ });
44
+ }
45
+
46
+ this._scheduleCheck(250);
47
+ },
48
+
49
+ _scheduleCheck(delayMs = 0) {
50
+ if (this.checkTimer) {
51
+ this.adapter.clearTimeout(this.checkTimer);
52
+ this.checkTimer = null;
53
+ }
54
+
55
+ this.checkTimer = this.adapter.setTimeout(() => {
56
+ this.checkTimer = null;
57
+ void this._checkSolarInsights();
58
+ }, delayMs);
59
+ },
60
+
61
+ _scheduleDailyReset() {
62
+ if (this.resetTimer) {
63
+ this.adapter.clearTimeout(this.resetTimer);
64
+ this.resetTimer = null;
65
+ }
66
+
67
+ const now = new Date();
68
+ const next = new Date(now);
69
+ next.setDate(now.getDate() + 1);
70
+ next.setHours(0, 0, 5, 0);
71
+
72
+ const delay = Math.max(1000, next.getTime() - now.getTime());
73
+
74
+ this.resetTimer = this.adapter.setTimeout(async () => {
75
+ try {
76
+ await this.adapter.setStateChangedAsync('analytics.insights.solar.results.solar_ran_today', {
77
+ val: false,
78
+ ack: true,
79
+ });
80
+
81
+ await this.adapter.setStateChangedAsync('analytics.insights.solar.results.estimated_gain_today_wh', {
82
+ val: 0,
83
+ ack: true,
84
+ });
85
+
86
+ await this.adapter.setStateChangedAsync('analytics.insights.solar.results.estimated_gain_today_kwh', {
87
+ val: 0,
88
+ ack: true,
89
+ });
90
+
91
+ await this.adapter.setStateChangedAsync('analytics.insights.solar.results.active_minutes_today', {
92
+ val: 0,
93
+ ack: true,
94
+ });
95
+
96
+ await this.adapter.setStateChangedAsync('analytics.insights.solar.results.peak_power_today_w', {
97
+ val: 0,
98
+ ack: true,
99
+ });
100
+
101
+ this.lastCheckTimestamp = null;
102
+
103
+ await this.adapter.setStateChangedAsync('analytics.insights.solar.debug.last_update', {
104
+ val: new Date().toISOString(),
105
+ ack: true,
106
+ });
107
+
108
+ await this.adapter.setStateChangedAsync('analytics.insights.solar.debug.last_recalculation_reason', {
109
+ val: I18n.translate('solar_insights_reason_daily_reset'),
110
+ ack: true,
111
+ });
112
+
113
+ await this.adapter.setStateChangedAsync('analytics.insights.solar.debug.debug_text', {
114
+ val: I18n.translate('solar_insights_debug_daily_reset_executed'),
115
+ ack: true,
116
+ });
117
+
118
+ this.adapter.log.debug('[solarInsightsHelper] Daily reset executed');
119
+ } catch (err) {
120
+ this.adapter.log.warn(`[solarInsightsHelper] Daily reset failed: ${err.message}`);
121
+ }
122
+
123
+ this._scheduleDailyReset();
124
+ }, delay);
125
+ },
126
+
127
+ async _subscribeStates() {
128
+ const ids = [
129
+ 'temperature.collector_temp_active',
130
+ 'temperature.surface_temp_active',
131
+ 'temperature.ground_temp_active',
132
+ 'temperature.flow_temp_active',
133
+ 'temperature.return_temp_active',
134
+ 'temperature.outside_temp_active',
135
+
136
+ 'temperature.collector.current',
137
+ 'temperature.surface.current',
138
+ 'temperature.ground.current',
139
+ 'temperature.flow.current',
140
+ 'temperature.return.current',
141
+ 'temperature.outside.current',
142
+
143
+ 'pump.live.flow_current_lh',
144
+ 'pump.live.current_power_w',
145
+ 'temperature.delta.surface_ground',
146
+
147
+ 'solar.active',
148
+ 'solar.extended.active',
149
+ 'solar.control_mode',
150
+
151
+ 'ai.weather.outputs.daily_summary',
152
+ ];
153
+
154
+ for (const id of ids) {
155
+ await this.adapter.subscribeStatesAsync(id);
156
+ }
157
+
158
+ this.adapter.log.debug('[solarInsightsHelper] Relevant states subscribed');
159
+ },
160
+
161
+ _isRelevantState(id) {
162
+ const ids = [
163
+ 'temperature.collector_temp_active',
164
+ 'temperature.surface_temp_active',
165
+ 'temperature.ground_temp_active',
166
+ 'temperature.flow_temp_active',
167
+ 'temperature.return_temp_active',
168
+ 'temperature.outside_temp_active',
169
+
170
+ 'temperature.collector.current',
171
+ 'temperature.surface.current',
172
+ 'temperature.ground.current',
173
+ 'temperature.flow.current',
174
+ 'temperature.return.current',
175
+ 'temperature.outside.current',
176
+
177
+ 'pump.live.flow_current_lh',
178
+ 'pump.live.current_power_w',
179
+ 'temperature.delta.surface_ground',
180
+
181
+ 'solar.active',
182
+ 'solar.extended.active',
183
+ 'solar.control_mode',
184
+
185
+ 'ai.weather.outputs.daily_summary',
186
+ ];
187
+
188
+ return ids.includes(id);
189
+ },
190
+
191
+ async _checkSolarInsights() {
192
+ try {
193
+ const collectorAvailable = await this._isSensorAvailable(
194
+ 'temperature.collector_temp_active',
195
+ 'temperature.collector.current',
196
+ );
197
+ const surfaceAvailable = await this._isSensorAvailable(
198
+ 'temperature.surface_temp_active',
199
+ 'temperature.surface.current',
200
+ );
201
+ const groundAvailable = await this._isSensorAvailable(
202
+ 'temperature.ground_temp_active',
203
+ 'temperature.ground.current',
204
+ );
205
+ const flowTempAvailable = await this._isSensorAvailable(
206
+ 'temperature.flow_temp_active',
207
+ 'temperature.flow.current',
208
+ );
209
+ const returnAvailable = await this._isSensorAvailable(
210
+ 'temperature.return_temp_active',
211
+ 'temperature.return.current',
212
+ );
213
+ const outsideAvailable = await this._isSensorAvailable(
214
+ 'temperature.outside_temp_active',
215
+ 'temperature.outside.current',
216
+ );
217
+
218
+ const collectorTemp = await this._readNumber('temperature.collector.current');
219
+ const surfaceTemp = await this._readNumber('temperature.surface.current');
220
+ const groundTemp = await this._readNumber('temperature.ground.current');
221
+ const outsideTemp = await this._readNumber('temperature.outside.current');
222
+ const currentFlowLh = await this._readNumber('pump.live.flow_current_lh');
223
+ const pumpCurrentPowerW = await this._readNumber('pump.live.current_power_w');
224
+ const surfaceGroundDeltaState = await this._readNumber('temperature.delta.surface_ground');
225
+ const weatherText = await this._readString('ai.weather.outputs.daily_summary');
226
+
227
+ const flowAvailable = flowTempAvailable || Number.isFinite(currentFlowLh);
228
+ const weatherAvailable = weatherText !== '';
229
+
230
+ const collectorUsed = collectorAvailable;
231
+ const surfaceUsed = surfaceAvailable;
232
+ const groundUsed = groundAvailable;
233
+ const flowUsed = flowAvailable;
234
+ const returnUsed = returnAvailable;
235
+
236
+ // Block 7: Outside / weather now participate in evaluation logic
237
+ const outsideUsed = outsideAvailable;
238
+ const weatherUsed = weatherAvailable;
239
+
240
+ const usedSensors = [];
241
+ const availableSensors = [];
242
+
243
+ if (collectorAvailable) {
244
+ availableSensors.push('collector');
245
+ }
246
+ if (surfaceAvailable) {
247
+ availableSensors.push('surface');
248
+ }
249
+ if (groundAvailable) {
250
+ availableSensors.push('ground');
251
+ }
252
+ if (flowAvailable) {
253
+ availableSensors.push('calculated_flow');
254
+ }
255
+ if (returnAvailable) {
256
+ availableSensors.push('return');
257
+ }
258
+ if (outsideAvailable) {
259
+ availableSensors.push('outside');
260
+ }
261
+ if (weatherAvailable) {
262
+ availableSensors.push('weather');
263
+ }
264
+
265
+ if (collectorUsed) {
266
+ usedSensors.push('collector');
267
+ }
268
+ if (surfaceUsed) {
269
+ usedSensors.push('surface');
270
+ }
271
+ if (groundUsed) {
272
+ usedSensors.push('ground');
273
+ }
274
+ if (flowUsed) {
275
+ usedSensors.push('calculated_flow');
276
+ }
277
+ if (returnUsed) {
278
+ usedSensors.push('return');
279
+ }
280
+ if (outsideUsed) {
281
+ usedSensors.push('outside');
282
+ }
283
+ if (weatherUsed) {
284
+ usedSensors.push('weather');
285
+ }
286
+
287
+ const solarStandardActive = await this._readBoolean('solar.active');
288
+ const solarExtendedActive = await this._readBoolean('solar.extended.active');
289
+ const oldSolarRanToday = await this._readBoolean('analytics.insights.solar.results.solar_ran_today');
290
+
291
+ const solarLogicActive = solarStandardActive || solarExtendedActive;
292
+ const solarRanToday = oldSolarRanToday || solarLogicActive;
293
+
294
+ let poolReferenceSource = 'none';
295
+ let poolReferenceTemp = null;
296
+
297
+ if (surfaceAvailable && groundAvailable && Number.isFinite(surfaceTemp) && Number.isFinite(groundTemp)) {
298
+ poolReferenceSource = 'surface_ground_average';
299
+ poolReferenceTemp = Number(((surfaceTemp + groundTemp) / 2).toFixed(2));
300
+ } else if (surfaceAvailable && Number.isFinite(surfaceTemp)) {
301
+ poolReferenceSource = 'surface';
302
+ poolReferenceTemp = surfaceTemp;
303
+ } else if (groundAvailable && Number.isFinite(groundTemp)) {
304
+ poolReferenceSource = 'ground';
305
+ poolReferenceTemp = groundTemp;
306
+ }
307
+
308
+ let deltaTUsed = null;
309
+ if (Number.isFinite(collectorTemp) && Number.isFinite(poolReferenceTemp)) {
310
+ deltaTUsed = Number((collectorTemp - poolReferenceTemp).toFixed(2));
311
+ }
312
+
313
+ // --- Block 3: Thermische Leistung berechnen ---
314
+ let thermalPowerW = null;
315
+ let thermalPowerKW = null;
316
+
317
+ if (Number.isFinite(deltaTUsed) && Number.isFinite(currentFlowLh) && currentFlowLh > 0) {
318
+ thermalPowerW = Number((currentFlowLh * deltaTUsed * 1.16).toFixed(2));
319
+ thermalPowerKW = Number((thermalPowerW / 1000).toFixed(3));
320
+ }
321
+
322
+ const solarEffectiveNow =
323
+ solarLogicActive &&
324
+ ((Number.isFinite(currentFlowLh) && currentFlowLh > 0) ||
325
+ (Number.isFinite(pumpCurrentPowerW) && pumpCurrentPowerW > 0) ||
326
+ (Number.isFinite(thermalPowerW) && thermalPowerW > 0));
327
+
328
+ const flowSource = Number.isFinite(currentFlowLh) ? 'pump.live.flow_current_lh' : 'none';
329
+ const weatherCorrectionActive = outsideUsed || weatherUsed;
330
+
331
+ let estimatedEfficiencyRatio = null;
332
+
333
+ if (Number.isFinite(thermalPowerW) && Number.isFinite(pumpCurrentPowerW) && pumpCurrentPowerW > 0) {
334
+ estimatedEfficiencyRatio = Number((thermalPowerW / pumpCurrentPowerW).toFixed(2));
335
+ }
336
+
337
+ // --- Block 4: Tagesertrag / aktive Minuten / Peak berechnen ---
338
+ const nowTs = Date.now();
339
+
340
+ let estimatedGainTodayWh = await this._readNumber(
341
+ 'analytics.insights.solar.results.estimated_gain_today_wh',
342
+ );
343
+ let activeMinutesToday = await this._readNumber('analytics.insights.solar.results.active_minutes_today');
344
+ let peakPowerTodayW = await this._readNumber('analytics.insights.solar.results.peak_power_today_w');
345
+
346
+ if (!Number.isFinite(estimatedGainTodayWh)) {
347
+ estimatedGainTodayWh = 0;
348
+ }
349
+ if (!Number.isFinite(activeMinutesToday)) {
350
+ activeMinutesToday = 0;
351
+ }
352
+ if (!Number.isFinite(peakPowerTodayW)) {
353
+ peakPowerTodayW = 0;
354
+ }
355
+
356
+ if (this.lastCheckTimestamp && solarEffectiveNow && Number.isFinite(thermalPowerW) && thermalPowerW > 0) {
357
+ const deltaHours = (nowTs - this.lastCheckTimestamp) / 3600000;
358
+
359
+ // Schutz gegen unrealistisch große Sprünge
360
+ if (deltaHours > 0 && deltaHours <= 0.5) {
361
+ estimatedGainTodayWh = Number((estimatedGainTodayWh + thermalPowerW * deltaHours).toFixed(2));
362
+ activeMinutesToday = Number((activeMinutesToday + deltaHours * 60).toFixed(2));
363
+ }
364
+ }
365
+
366
+ if (solarEffectiveNow && Number.isFinite(thermalPowerW) && thermalPowerW > peakPowerTodayW) {
367
+ peakPowerTodayW = Number(thermalPowerW.toFixed(2));
368
+ }
369
+
370
+ const estimatedGainTodayKWh = Number((estimatedGainTodayWh / 1000).toFixed(3));
371
+
372
+ // --- Block 5: Confidence / Qualitätsbewertung ---
373
+ let confidencePercent = 25;
374
+
375
+ if (
376
+ Number.isFinite(collectorTemp) &&
377
+ Number.isFinite(poolReferenceTemp) &&
378
+ Number.isFinite(currentFlowLh)
379
+ ) {
380
+ confidencePercent = 50;
381
+ }
382
+
383
+ if (surfaceUsed && groundUsed) {
384
+ confidencePercent = 70;
385
+ }
386
+
387
+ if (returnUsed) {
388
+ confidencePercent = Math.max(confidencePercent, 80);
389
+ }
390
+
391
+ if (outsideUsed) {
392
+ confidencePercent = Math.max(confidencePercent, 85);
393
+ }
394
+
395
+ if (weatherUsed) {
396
+ confidencePercent = Math.max(confidencePercent, 90);
397
+ }
398
+
399
+ let surfaceGroundDelta = null;
400
+ if (Number.isFinite(surfaceGroundDeltaState)) {
401
+ surfaceGroundDelta = surfaceGroundDeltaState;
402
+ } else if (Number.isFinite(surfaceTemp) && Number.isFinite(groundTemp)) {
403
+ surfaceGroundDelta = Number((surfaceTemp - groundTemp).toFixed(2));
404
+ }
405
+
406
+ const summaryJson = {
407
+ mode: 'estimated_daily_gain',
408
+ solar_ran_today: solarRanToday,
409
+ solar_effective_now: solarEffectiveNow,
410
+ pool_reference_source: poolReferenceSource,
411
+ flow_source: flowSource,
412
+ weather_correction_active: weatherCorrectionActive,
413
+ confidence_percent: confidencePercent,
414
+ used_sensors: usedSensors,
415
+ values: {
416
+ collector_temp_used: Number.isFinite(collectorTemp) ? collectorTemp : null,
417
+ pool_reference_temp_used: Number.isFinite(poolReferenceTemp) ? poolReferenceTemp : null,
418
+ delta_t_used: Number.isFinite(deltaTUsed) ? deltaTUsed : null,
419
+ surface_ground_delta: Number.isFinite(surfaceGroundDelta) ? surfaceGroundDelta : null,
420
+ outside_temp_used: Number.isFinite(outsideTemp) ? outsideTemp : null,
421
+ flow_lh_used: Number.isFinite(currentFlowLh) ? currentFlowLh : null,
422
+ pump_power_w_used: Number.isFinite(pumpCurrentPowerW) ? pumpCurrentPowerW : null,
423
+ estimated_thermal_power_w: Number.isFinite(thermalPowerW) ? thermalPowerW : null,
424
+ estimated_thermal_power_kw: Number.isFinite(thermalPowerKW) ? thermalPowerKW : null,
425
+ estimated_efficiency_ratio: Number.isFinite(estimatedEfficiencyRatio)
426
+ ? estimatedEfficiencyRatio
427
+ : null,
428
+ estimated_gain_today_wh: Number.isFinite(estimatedGainTodayWh) ? estimatedGainTodayWh : null,
429
+ estimated_gain_today_kwh: Number.isFinite(estimatedGainTodayKWh) ? estimatedGainTodayKWh : null,
430
+ active_minutes_today: Number.isFinite(activeMinutesToday) ? activeMinutesToday : null,
431
+ peak_power_today_w: Number.isFinite(peakPowerTodayW) ? peakPowerTodayW : null,
432
+ },
433
+ note: I18n.translate('solar_insights_summary_note_block_7'),
434
+ };
435
+
436
+ const summaryHtml = [
437
+ '<div>',
438
+ `<b>${I18n.translate('solar_insights_label_mode')}:</b> ${I18n.translate('solar_insights_mode_estimated_daily_gain')}<br>`,
439
+ `<b>${I18n.translate('solar_insights_label_solar_ran_today')}:</b> ${solarRanToday}<br>`,
440
+ `<b>${I18n.translate('solar_insights_label_solar_effective_now')}:</b> ${solarEffectiveNow}<br>`,
441
+ `<b>${I18n.translate('solar_insights_label_pool_reference_source')}:</b> ${poolReferenceSource}<br>`,
442
+ `<b>${I18n.translate('solar_insights_label_flow_source')}:</b> ${flowSource}<br>`,
443
+ `<b>${I18n.translate('solar_insights_label_weather_correction_active')}:</b> ${weatherCorrectionActive}<br>`,
444
+ `<b>${I18n.translate('solar_insights_label_confidence')}:</b> ${confidencePercent} %<br>`,
445
+ `<b>${I18n.translate('solar_insights_label_used_sensors')}:</b> ${usedSensors.join(', ') || I18n.translate('solar_insights_value_none')}<br>`,
446
+ `<b>${I18n.translate('solar_insights_label_collector')}:</b> ${Number.isFinite(collectorTemp) ? `${collectorTemp} °C` : I18n.translate('solar_insights_value_na')}<br>`,
447
+ `<b>${I18n.translate('solar_insights_label_pool_reference')}:</b> ${Number.isFinite(poolReferenceTemp) ? `${poolReferenceTemp} °C` : I18n.translate('solar_insights_value_na')}<br>`,
448
+ `<b>${I18n.translate('solar_insights_label_delta_t')}:</b> ${Number.isFinite(deltaTUsed) ? `${deltaTUsed} K` : I18n.translate('solar_insights_value_na')}<br>`,
449
+ `<b>${I18n.translate('solar_insights_label_surface_ground_delta')}:</b> ${Number.isFinite(surfaceGroundDelta) ? `${surfaceGroundDelta} K` : I18n.translate('solar_insights_value_na')}<br>`,
450
+ `<b>${I18n.translate('solar_insights_label_outside')}:</b> ${Number.isFinite(outsideTemp) ? `${outsideTemp} °C` : I18n.translate('solar_insights_value_na')}<br>`,
451
+ `<b>${I18n.translate('solar_insights_label_flow')}:</b> ${Number.isFinite(currentFlowLh) ? `${currentFlowLh} l/h` : I18n.translate('solar_insights_value_na')}<br>`,
452
+ `<b>${I18n.translate('solar_insights_label_pump_power')}:</b> ${Number.isFinite(pumpCurrentPowerW) ? `${pumpCurrentPowerW} W` : I18n.translate('solar_insights_value_na')}<br>`,
453
+ `<b>${I18n.translate('solar_insights_label_thermal_power')}:</b> ${Number.isFinite(thermalPowerW) ? `${thermalPowerW} W (${thermalPowerKW} kW)` : I18n.translate('solar_insights_value_na')}<br>`,
454
+ `<b>${I18n.translate('solar_insights_label_estimated_efficiency_ratio')}:</b> ${Number.isFinite(estimatedEfficiencyRatio) ? estimatedEfficiencyRatio : I18n.translate('solar_insights_value_na')}<br>`,
455
+ `<b>${I18n.translate('solar_insights_label_gain_today')}:</b> ${Number.isFinite(estimatedGainTodayWh) ? `${estimatedGainTodayWh} Wh (${estimatedGainTodayKWh} kWh)` : I18n.translate('solar_insights_value_na')}<br>`,
456
+ `<b>${I18n.translate('solar_insights_label_active_minutes_today')}:</b> ${Number.isFinite(activeMinutesToday) ? `${activeMinutesToday} min` : I18n.translate('solar_insights_value_na')}<br>`,
457
+ `<b>${I18n.translate('solar_insights_label_peak_power_today')}:</b> ${Number.isFinite(peakPowerTodayW) ? `${peakPowerTodayW} W` : I18n.translate('solar_insights_value_na')}<br>`,
458
+ `<b>${I18n.translate('solar_insights_label_note')}:</b> ${I18n.translate('solar_insights_html_note_block_7')}`,
459
+ '</div>',
460
+ ].join('');
461
+
462
+ // --- Inputs: available ---
463
+ await this.adapter.setStateChangedAsync('analytics.insights.solar.inputs.collector_available', {
464
+ val: collectorAvailable,
465
+ ack: true,
466
+ });
467
+ await this.adapter.setStateChangedAsync('analytics.insights.solar.inputs.surface_available', {
468
+ val: surfaceAvailable,
469
+ ack: true,
470
+ });
471
+ await this.adapter.setStateChangedAsync('analytics.insights.solar.inputs.ground_available', {
472
+ val: groundAvailable,
473
+ ack: true,
474
+ });
475
+ await this.adapter.setStateChangedAsync('analytics.insights.solar.inputs.flow_available', {
476
+ val: flowAvailable,
477
+ ack: true,
478
+ });
479
+ await this.adapter.setStateChangedAsync('analytics.insights.solar.inputs.return_available', {
480
+ val: returnAvailable,
481
+ ack: true,
482
+ });
483
+ await this.adapter.setStateChangedAsync('analytics.insights.solar.inputs.outside_available', {
484
+ val: outsideAvailable,
485
+ ack: true,
486
+ });
487
+ await this.adapter.setStateChangedAsync('analytics.insights.solar.inputs.weather_available', {
488
+ val: weatherAvailable,
489
+ ack: true,
490
+ });
491
+
492
+ // --- Inputs: used ---
493
+ await this.adapter.setStateChangedAsync('analytics.insights.solar.inputs.collector_used', {
494
+ val: collectorUsed,
495
+ ack: true,
496
+ });
497
+ await this.adapter.setStateChangedAsync('analytics.insights.solar.inputs.surface_used', {
498
+ val: surfaceUsed,
499
+ ack: true,
500
+ });
501
+ await this.adapter.setStateChangedAsync('analytics.insights.solar.inputs.ground_used', {
502
+ val: groundUsed,
503
+ ack: true,
504
+ });
505
+ await this.adapter.setStateChangedAsync('analytics.insights.solar.inputs.flow_used', {
506
+ val: flowUsed,
507
+ ack: true,
508
+ });
509
+ await this.adapter.setStateChangedAsync('analytics.insights.solar.inputs.return_used', {
510
+ val: returnUsed,
511
+ ack: true,
512
+ });
513
+ await this.adapter.setStateChangedAsync('analytics.insights.solar.inputs.outside_used', {
514
+ val: outsideUsed,
515
+ ack: true,
516
+ });
517
+ await this.adapter.setStateChangedAsync('analytics.insights.solar.inputs.weather_used', {
518
+ val: weatherUsed,
519
+ ack: true,
520
+ });
521
+
522
+ await this.adapter.setStateChangedAsync('analytics.insights.solar.inputs.used_sensors_text', {
523
+ val: usedSensors.join(', '),
524
+ ack: true,
525
+ });
526
+
527
+ await this.adapter.setStateChangedAsync('analytics.insights.solar.inputs.used_sensors_json', {
528
+ val: JSON.stringify({
529
+ available: availableSensors,
530
+ used: usedSensors,
531
+ mode: 'estimated_daily_gain',
532
+ block: 7,
533
+ }),
534
+ ack: true,
535
+ });
536
+
537
+ // --- Results: status ---
538
+ await this.adapter.setStateChangedAsync('analytics.insights.solar.results.solar_ran_today', {
539
+ val: solarRanToday,
540
+ ack: true,
541
+ });
542
+ await this.adapter.setStateChangedAsync('analytics.insights.solar.results.analysis_active', {
543
+ val: true,
544
+ ack: true,
545
+ });
546
+ await this.adapter.setStateChangedAsync('analytics.insights.solar.results.solar_effective_now', {
547
+ val: solarEffectiveNow,
548
+ ack: true,
549
+ });
550
+ await this.adapter.setStateChangedAsync('analytics.insights.solar.results.solar_gain_state', {
551
+ val: solarRanToday
552
+ ? I18n.translate('solar_insights_status_estimated_daily_gain_ready')
553
+ : I18n.translate('solar_insights_status_no_solar_runtime_today'),
554
+ ack: true,
555
+ });
556
+
557
+ // --- Results: direct values ---
558
+ await this.adapter.setStateChangedAsync('analytics.insights.solar.results.collector_temp_used', {
559
+ val: Number.isFinite(collectorTemp) ? collectorTemp : null,
560
+ ack: true,
561
+ });
562
+ await this.adapter.setStateChangedAsync('analytics.insights.solar.results.pool_reference_temp_used', {
563
+ val: Number.isFinite(poolReferenceTemp) ? poolReferenceTemp : null,
564
+ ack: true,
565
+ });
566
+ await this.adapter.setStateChangedAsync('analytics.insights.solar.results.delta_t_used', {
567
+ val: Number.isFinite(deltaTUsed) ? deltaTUsed : null,
568
+ ack: true,
569
+ });
570
+ await this.adapter.setStateChangedAsync('analytics.insights.solar.results.surface_ground_delta', {
571
+ val: Number.isFinite(surfaceGroundDelta) ? surfaceGroundDelta : null,
572
+ ack: true,
573
+ });
574
+ await this.adapter.setStateChangedAsync('analytics.insights.solar.results.outside_temp_used', {
575
+ val: Number.isFinite(outsideTemp) ? outsideTemp : null,
576
+ ack: true,
577
+ });
578
+ await this.adapter.setStateChangedAsync('analytics.insights.solar.results.flow_lh_used', {
579
+ val: Number.isFinite(currentFlowLh) ? currentFlowLh : null,
580
+ ack: true,
581
+ });
582
+ await this.adapter.setStateChangedAsync('analytics.insights.solar.results.pump_power_w_used', {
583
+ val: Number.isFinite(pumpCurrentPowerW) ? pumpCurrentPowerW : null,
584
+ ack: true,
585
+ });
586
+
587
+ // --- Results: thermal power ---
588
+ await this.adapter.setStateChangedAsync('analytics.insights.solar.results.estimated_thermal_power_w', {
589
+ val: Number.isFinite(thermalPowerW) ? thermalPowerW : null,
590
+ ack: true,
591
+ });
592
+ await this.adapter.setStateChangedAsync('analytics.insights.solar.results.estimated_thermal_power_kw', {
593
+ val: Number.isFinite(thermalPowerKW) ? thermalPowerKW : null,
594
+ ack: true,
595
+ });
596
+
597
+ await this.adapter.setStateChangedAsync('analytics.insights.solar.results.estimated_efficiency_ratio', {
598
+ val: Number.isFinite(estimatedEfficiencyRatio) ? estimatedEfficiencyRatio : null,
599
+ ack: true,
600
+ });
601
+
602
+ await this.adapter.setStateChangedAsync('analytics.insights.solar.results.estimated_gain_today_wh', {
603
+ val: Number.isFinite(estimatedGainTodayWh) ? estimatedGainTodayWh : 0,
604
+ ack: true,
605
+ });
606
+
607
+ await this.adapter.setStateChangedAsync('analytics.insights.solar.results.estimated_gain_today_kwh', {
608
+ val: Number.isFinite(estimatedGainTodayKWh) ? estimatedGainTodayKWh : 0,
609
+ ack: true,
610
+ });
611
+
612
+ await this.adapter.setStateChangedAsync('analytics.insights.solar.results.active_minutes_today', {
613
+ val: Number.isFinite(activeMinutesToday) ? activeMinutesToday : 0,
614
+ ack: true,
615
+ });
616
+
617
+ await this.adapter.setStateChangedAsync('analytics.insights.solar.results.peak_power_today_w', {
618
+ val: Number.isFinite(peakPowerTodayW) ? peakPowerTodayW : 0,
619
+ ack: true,
620
+ });
621
+
622
+ await this.adapter.setStateChangedAsync('analytics.insights.solar.results.summary_json', {
623
+ val: JSON.stringify(summaryJson),
624
+ ack: true,
625
+ });
626
+ await this.adapter.setStateChangedAsync('analytics.insights.solar.results.summary_html', {
627
+ val: summaryHtml,
628
+ ack: true,
629
+ });
630
+
631
+ // --- Calculation ---
632
+ await this.adapter.setStateChangedAsync('analytics.insights.solar.calculation.mode', {
633
+ val: I18n.translate('solar_insights_mode_estimated_daily_gain'),
634
+ ack: true,
635
+ });
636
+ await this.adapter.setStateChangedAsync('analytics.insights.solar.calculation.quality_level', {
637
+ val:
638
+ confidencePercent >= 85
639
+ ? I18n.translate('solar_insights_quality_advanced')
640
+ : confidencePercent >= 70
641
+ ? I18n.translate('solar_insights_quality_enhanced')
642
+ : I18n.translate('solar_insights_quality_basic'),
643
+ ack: true,
644
+ });
645
+ await this.adapter.setStateChangedAsync('analytics.insights.solar.calculation.confidence_percent', {
646
+ val: confidencePercent,
647
+ ack: true,
648
+ });
649
+ await this.adapter.setStateChangedAsync('analytics.insights.solar.calculation.pool_reference_source', {
650
+ val: poolReferenceSource,
651
+ ack: true,
652
+ });
653
+ await this.adapter.setStateChangedAsync('analytics.insights.solar.calculation.flow_source', {
654
+ val: flowSource,
655
+ ack: true,
656
+ });
657
+ await this.adapter.setStateChangedAsync('analytics.insights.solar.calculation.weather_correction_active', {
658
+ val: weatherCorrectionActive,
659
+ ack: true,
660
+ });
661
+ await this.adapter.setStateChangedAsync('analytics.insights.solar.calculation.note', {
662
+ val: I18n.translate('solar_insights_calculation_note_block_7'),
663
+ ack: true,
664
+ });
665
+
666
+ // --- Debug ---
667
+ await this.adapter.setStateChangedAsync('analytics.insights.solar.debug.last_recalculation_reason', {
668
+ val: I18n.translate('solar_insights_reason_block_7_update'),
669
+ ack: true,
670
+ });
671
+ await this.adapter.setStateChangedAsync('analytics.insights.solar.debug.last_valid_mode', {
672
+ val: 'estimated_daily_gain',
673
+ ack: true,
674
+ });
675
+ await this.adapter.setStateChangedAsync('analytics.insights.solar.debug.last_invalid_reason', {
676
+ val: '',
677
+ ack: true,
678
+ });
679
+ await this.adapter.setStateChangedAsync('analytics.insights.solar.debug.debug_text', {
680
+ val: `${I18n.translate('solar_insights_debug_block_7_updated')} ${usedSensors.join(', ') || I18n.translate('solar_insights_value_none')}`,
681
+ ack: true,
682
+ });
683
+
684
+ this.lastCheckTimestamp = nowTs;
685
+ this.adapter.log.debug('[solarInsightsHelper] Block 7 updated successfully');
686
+ } catch (err) {
687
+ this.adapter.log.warn(`[solarInsightsHelper] Error in check: ${err.message}`);
688
+
689
+ await this.adapter.setStateChangedAsync('analytics.insights.solar.debug.last_update', {
690
+ val: new Date().toISOString(),
691
+ ack: true,
692
+ });
693
+ await this.adapter.setStateChangedAsync('analytics.insights.solar.debug.last_invalid_reason', {
694
+ val: err.message,
695
+ ack: true,
696
+ });
697
+ await this.adapter.setStateChangedAsync('analytics.insights.solar.debug.debug_text', {
698
+ val: `${I18n.translate('solar_insights_debug_block_7_error')} ${err.message}`,
699
+ ack: true,
700
+ });
701
+ }
702
+ },
703
+
704
+ async _isSensorAvailable(activeId, valueId) {
705
+ const active = await this._readBoolean(activeId);
706
+ const value = await this._readNumber(valueId);
707
+
708
+ return active && Number.isFinite(value);
709
+ },
710
+
711
+ async _readState(id) {
712
+ try {
713
+ return await this.adapter.getStateAsync(id);
714
+ } catch {
715
+ return null;
716
+ }
717
+ },
718
+
719
+ async _readBoolean(id) {
720
+ const state = await this._readState(id);
721
+
722
+ if (!state) {
723
+ return false;
724
+ }
725
+
726
+ return state.val === true;
727
+ },
728
+
729
+ async _readNumber(id) {
730
+ const state = await this._readState(id);
731
+
732
+ if (!state || state.val === null || state.val === undefined || state.val === '') {
733
+ return null;
734
+ }
735
+
736
+ const value = Number(state.val);
737
+ return Number.isFinite(value) ? value : null;
738
+ },
739
+
740
+ async _readString(id) {
741
+ const state = await this._readState(id);
742
+
743
+ if (!state || state.val === null || state.val === undefined) {
744
+ return '';
745
+ }
746
+
747
+ return String(state.val);
748
+ },
749
+
750
+ cleanup() {
751
+ if (this.checkTimer) {
752
+ this.adapter.clearTimeout(this.checkTimer);
753
+ this.checkTimer = null;
754
+ }
755
+
756
+ if (this.resetTimer) {
757
+ this.adapter.clearTimeout(this.resetTimer);
758
+ this.resetTimer = null;
759
+ }
760
+ },
761
+ };
762
+
763
+ module.exports = solarInsightsHelper;
764
+
765
+ // i18n keys required:
766
+ // solar_insights_summary_note_block_7
767
+ // solar_insights_label_mode
768
+ // solar_insights_label_solar_ran_today
769
+ // solar_insights_label_solar_effective_now
770
+ // solar_insights_label_pool_reference_source
771
+ // solar_insights_label_flow_source
772
+ // solar_insights_label_weather_correction_active
773
+ // solar_insights_label_confidence
774
+ // solar_insights_label_used_sensors
775
+ // solar_insights_label_collector
776
+ // solar_insights_label_pool_reference
777
+ // solar_insights_label_delta_t
778
+ // solar_insights_label_surface_ground_delta
779
+ // solar_insights_label_outside
780
+ // solar_insights_label_flow
781
+ // solar_insights_label_pump_power
782
+ // solar_insights_label_thermal_power
783
+ // solar_insights_label_estimated_efficiency_ratio
784
+ // solar_insights_label_gain_today
785
+ // solar_insights_label_active_minutes_today
786
+ // solar_insights_label_peak_power_today
787
+ // solar_insights_label_note
788
+ // solar_insights_value_none
789
+ // solar_insights_value_na
790
+ // solar_insights_reason_daily_reset
791
+ // solar_insights_debug_daily_reset_executed
792
+ // solar_insights_status_estimated_daily_gain_ready
793
+ // solar_insights_status_no_solar_runtime_today
794
+ // solar_insights_mode_estimated_daily_gain
795
+ // solar_insights_quality_advanced
796
+ // solar_insights_quality_enhanced
797
+ // solar_insights_quality_basic
798
+ // solar_insights_calculation_note_block_7
799
+ // solar_insights_reason_block_7_update
800
+ // solar_insights_debug_block_7_updated
801
+ // solar_insights_debug_block_7_error
802
+ // solar_insights_html_note_block_7