iobroker.poolcontrol 1.3.32 → 1.3.34

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.
@@ -2,8 +2,14 @@
2
2
 
3
3
  const { I18n } = require('@iobroker/adapter-core');
4
4
 
5
- const MAX_HISTORY_AGE_MS = 30 * 24 * 60 * 60 * 1000;
5
+ const MAX_HISTORY_AGE_MS = 7 * 24 * 60 * 60 * 1000;
6
+ const MAX_HISTORY_SAMPLES = 672;
7
+ const MAX_HISTORY_BYTES = 64 * 1024;
6
8
  const MIN_SAMPLE_INTERVAL_MS = 15 * 60 * 1000;
9
+ const MAX_DAILY_HISTORY_AGE_MS = 32 * 24 * 60 * 60 * 1000;
10
+ const MAX_DAILY_HISTORY_SAMPLES = 32;
11
+ const MAX_DAILY_HISTORY_BYTES = 8 * 1024;
12
+ const DAILY_REFERENCE_TOLERANCE_MS = 4 * 24 * 60 * 60 * 1000;
7
13
 
8
14
  const chemistryTdsHelper = {
9
15
  adapter: null,
@@ -170,7 +176,7 @@ const chemistryTdsHelper = {
170
176
  return;
171
177
  }
172
178
 
173
- await this._processValue(source, rawValue, reason, true);
179
+ await this._processValue(source, rawValue, reason, source === 'manual');
174
180
  },
175
181
 
176
182
  _scheduleEvaluation(reason, delayMs = 500) {
@@ -289,7 +295,7 @@ const chemistryTdsHelper = {
289
295
 
290
296
  const history = await this._updateHistory(value, now, forceSample);
291
297
  const reference = await this._updateReference(value, now);
292
- const trend = await this._calculateTrend(value, now, history);
298
+ const trend = await this._calculateTrend(value, now, history.samples, history.dailySamples);
293
299
  const evaluation = await this._evaluateTds(value, reference, trend);
294
300
 
295
301
  await this._writeTrend(trend);
@@ -373,16 +379,17 @@ const chemistryTdsHelper = {
373
379
 
374
380
  async _updateHistory(value, now, forceSample) {
375
381
  let samples = await this._readJsonArray('chemistry.tds.history.samples_json');
382
+ const dailySeedSamples = samples;
376
383
  const nowTs = now.getTime();
377
384
  const minTs = nowTs - MAX_HISTORY_AGE_MS;
378
385
 
379
- samples = samples.filter(
380
- sample => sample && Number(sample.ts) >= minTs && Number.isFinite(Number(sample.value)),
381
- );
386
+ samples = samples.filter(sample => sample.ts >= minTs && sample.ts <= nowTs);
382
387
 
383
388
  const newest = samples.length ? samples[samples.length - 1] : null;
384
389
  const newestTs = newest ? Number(newest.ts) : 0;
385
- const shouldStore = forceSample || !newest || nowTs - newestTs >= MIN_SAMPLE_INTERVAL_MS;
390
+ const withinSampleInterval = newest && nowTs - newestTs < MIN_SAMPLE_INTERVAL_MS;
391
+ const sameValue = newest && Number(newest.value) === value;
392
+ const shouldStore = !newest || !withinSampleInterval || (forceSample && !sameValue);
386
393
 
387
394
  if (shouldStore) {
388
395
  samples.push({
@@ -392,9 +399,12 @@ const chemistryTdsHelper = {
392
399
  });
393
400
  }
394
401
 
395
- samples = samples.filter(sample => sample && Number(sample.ts) >= minTs);
402
+ samples = samples.filter(sample => sample.ts >= minTs && sample.ts <= nowTs).slice(-MAX_HISTORY_SAMPLES);
403
+
404
+ const preparedHistory = this._prepareHistoryForWrite(samples, 'chemistry.tds.history.samples_json');
405
+ samples = preparedHistory.samples;
396
406
 
397
- await this._setString('chemistry.tds.history.samples_json', JSON.stringify(samples));
407
+ await this._setString('chemistry.tds.history.samples_json', preparedHistory.json);
398
408
  await this._setNumber('chemistry.tds.history.samples_count', samples.length);
399
409
 
400
410
  if (samples.length) {
@@ -410,7 +420,8 @@ const chemistryTdsHelper = {
410
420
  );
411
421
  }
412
422
 
413
- return samples;
423
+ const dailySamples = await this._updateDailyHistory(dailySeedSamples, value, now, shouldStore);
424
+ return { samples, dailySamples };
414
425
  },
415
426
 
416
427
  async _updateReference(value, now) {
@@ -457,12 +468,16 @@ const chemistryTdsHelper = {
457
468
  this._scheduleEvaluation('reference_reset', 500);
458
469
  },
459
470
 
460
- async _calculateTrend(currentValue, now, samples) {
471
+ async _calculateTrend(currentValue, now, samples, dailySamples) {
461
472
  const nowTs = now.getTime();
462
473
 
463
474
  const ref24h = this._findReferenceSample(samples, nowTs - 24 * 60 * 60 * 1000);
464
475
  const ref7d = this._findReferenceSample(samples, nowTs - 7 * 24 * 60 * 60 * 1000);
465
- const ref30d = this._findReferenceSample(samples, nowTs - 30 * 24 * 60 * 60 * 1000);
476
+ const ref30d = this._findReferenceSample(
477
+ dailySamples,
478
+ nowTs - 30 * 24 * 60 * 60 * 1000,
479
+ DAILY_REFERENCE_TOLERANCE_MS,
480
+ );
466
481
 
467
482
  const delta24h = ref24h ? currentValue - ref24h.value : 0;
468
483
  const delta7d = ref7d ? currentValue - ref7d.value : 0;
@@ -483,7 +498,7 @@ const chemistryTdsHelper = {
483
498
  };
484
499
  },
485
500
 
486
- _findReferenceSample(samples, targetTs) {
501
+ _findReferenceSample(samples, targetTs, toleranceMs = null) {
487
502
  if (!samples.length) {
488
503
  return null;
489
504
  }
@@ -493,7 +508,7 @@ const chemistryTdsHelper = {
493
508
 
494
509
  for (const sample of samples) {
495
510
  const ts = Number(sample.ts);
496
- const value = Number(sample.value);
511
+ const value = Number(sample.value ?? sample.last);
497
512
 
498
513
  if (!Number.isFinite(ts) || !Number.isFinite(value)) {
499
514
  continue;
@@ -511,7 +526,7 @@ const chemistryTdsHelper = {
511
526
  }
512
527
  }
513
528
 
514
- return best;
529
+ return toleranceMs === null || bestDistance <= toleranceMs ? best : null;
515
530
  },
516
531
 
517
532
  _getOverallDirection(ref24h, ref7d, ref30d, delta24h, delta7d, delta30d) {
@@ -768,17 +783,264 @@ const chemistryTdsHelper = {
768
783
  return !!state?.val;
769
784
  },
770
785
 
786
+ async _updateDailyHistory(shortTermSamples, value, now, sampleStored) {
787
+ const id = 'chemistry.tds.history.daily_json';
788
+ let dailySamples = await this._readDailyJson(id);
789
+ const nowTs = now.getTime();
790
+ const minTs = nowTs - MAX_DAILY_HISTORY_AGE_MS;
791
+ const initializeFromSeeds = dailySamples.length === 0;
792
+
793
+ if (initializeFromSeeds) {
794
+ const legacyValueState = await this.adapter.getStateAsync('chemistry.tds.trend.reference_30d_value');
795
+ const legacyAtState = await this.adapter.getStateAsync('chemistry.tds.trend.reference_30d_at');
796
+ const legacyValue = Number(legacyValueState?.val);
797
+ const legacyTs = Number(legacyAtState?.val);
798
+
799
+ if (
800
+ Number.isFinite(legacyValue) &&
801
+ legacyValue >= 0 &&
802
+ legacyValue <= 10000 &&
803
+ Number.isFinite(legacyTs) &&
804
+ legacyTs >= minTs &&
805
+ legacyTs <= nowTs &&
806
+ !shortTermSamples.some(sample => this._dayKey(sample.ts) === this._dayKey(legacyTs))
807
+ ) {
808
+ this._addDailySample(dailySamples, legacyTs, legacyValue);
809
+ }
810
+
811
+ for (const sample of shortTermSamples) {
812
+ if (sample.ts >= minTs && sample.ts <= nowTs) {
813
+ this._addDailySample(dailySamples, sample.ts, sample.value);
814
+ }
815
+ }
816
+ if (sampleStored) {
817
+ this._addDailySample(dailySamples, nowTs, value);
818
+ }
819
+ } else if (sampleStored) {
820
+ this._addDailySample(dailySamples, nowTs, value);
821
+ }
822
+
823
+ dailySamples = dailySamples
824
+ .filter(sample => sample.ts >= minTs && sample.ts <= nowTs)
825
+ .sort((a, b) => a.ts - b.ts)
826
+ .slice(-MAX_DAILY_HISTORY_SAMPLES);
827
+
828
+ const preparedHistory = this._prepareDailyHistoryForWrite(dailySamples, id);
829
+ await this._setString(id, preparedHistory.json);
830
+ return preparedHistory.samples;
831
+ },
832
+
833
+ _addDailySample(dailySamples, sampleTs, value) {
834
+ const day = this._dayKey(sampleTs);
835
+ const existing = dailySamples.find(sample => sample.day === day);
836
+
837
+ if (!existing) {
838
+ dailySamples.push({
839
+ day,
840
+ ts: this._dayStartTs(sampleTs),
841
+ min: value,
842
+ max: value,
843
+ avg: value,
844
+ last: value,
845
+ count: 1,
846
+ });
847
+ return;
848
+ }
849
+
850
+ existing.min = Math.min(existing.min, value);
851
+ existing.max = Math.max(existing.max, value);
852
+ existing.avg = (existing.avg * existing.count + value) / (existing.count + 1);
853
+ existing.last = value;
854
+ existing.count += 1;
855
+ },
856
+
857
+ async _readDailyJson(id) {
858
+ const state = await this.adapter.getStateAsync(id);
859
+ const rawValue = state?.val;
860
+
861
+ if (rawValue === null || rawValue === undefined || rawValue === '') {
862
+ return [];
863
+ }
864
+
865
+ if (typeof rawValue !== 'string') {
866
+ this.adapter.log.warn(`[chemistryTdsHelper] Ignoring non-string daily history state ${id}.`);
867
+ return [];
868
+ }
869
+
870
+ if (Buffer.byteLength(rawValue, 'utf8') > MAX_DAILY_HISTORY_BYTES) {
871
+ this.adapter.log.warn(`[chemistryTdsHelper] Ignoring oversized daily history state ${id} (> 8 KB).`);
872
+ return [];
873
+ }
874
+
875
+ try {
876
+ const parsed = JSON.parse(rawValue);
877
+ if (!Array.isArray(parsed)) {
878
+ return [];
879
+ }
880
+
881
+ const nowTs = Date.now();
882
+ const normalized = parsed
883
+ .map(sample => {
884
+ const sourceTs = Number(sample?.ts);
885
+ const legacyValue = Number(sample?.value);
886
+ const min = Number(sample?.min ?? legacyValue);
887
+ const max = Number(sample?.max ?? legacyValue);
888
+ const avg = Number(sample?.avg ?? legacyValue);
889
+ const last = Number(sample?.last ?? legacyValue);
890
+ const count = sample?.count === undefined ? 1 : Number(sample.count);
891
+
892
+ if (!Number.isFinite(sourceTs) || sourceTs <= 0 || sourceTs > nowTs) {
893
+ return null;
894
+ }
895
+ if (
896
+ !Number.isFinite(min) ||
897
+ !Number.isFinite(max) ||
898
+ !Number.isFinite(avg) ||
899
+ !Number.isFinite(last) ||
900
+ min < 0 ||
901
+ max > 10000 ||
902
+ min > max ||
903
+ avg < min ||
904
+ avg > max ||
905
+ last < min ||
906
+ last > max
907
+ ) {
908
+ return null;
909
+ }
910
+ if (!Number.isInteger(count) || count < 1 || count > 1000000) {
911
+ return null;
912
+ }
913
+
914
+ return {
915
+ day: this._dayKey(sourceTs),
916
+ ts: this._dayStartTs(sourceTs),
917
+ min,
918
+ max,
919
+ avg,
920
+ last,
921
+ count,
922
+ };
923
+ })
924
+ .filter(Boolean)
925
+ .sort((a, b) => a.ts - b.ts);
926
+
927
+ const byDay = new Map();
928
+ for (const sample of normalized) {
929
+ if (!byDay.has(sample.day)) {
930
+ byDay.set(sample.day, sample);
931
+ }
932
+ }
933
+ return [...byDay.values()].slice(-MAX_DAILY_HISTORY_SAMPLES);
934
+ } catch {
935
+ return [];
936
+ }
937
+ },
938
+
939
+ _prepareDailyHistoryForWrite(samples, id) {
940
+ let limitedSamples = samples.slice(-MAX_DAILY_HISTORY_SAMPLES);
941
+ let json = JSON.stringify(limitedSamples);
942
+ let trimmedForSize = false;
943
+
944
+ while (limitedSamples.length && Buffer.byteLength(json, 'utf8') > MAX_DAILY_HISTORY_BYTES) {
945
+ limitedSamples = limitedSamples.slice(1);
946
+ json = JSON.stringify(limitedSamples);
947
+ trimmedForSize = true;
948
+ }
949
+
950
+ if (trimmedForSize) {
951
+ this.adapter.log.warn(`[chemistryTdsHelper] Daily history ${id} was trimmed to stay within 8 KB.`);
952
+ }
953
+
954
+ if (Buffer.byteLength(json, 'utf8') > MAX_DAILY_HISTORY_BYTES) {
955
+ this.adapter.log.warn(`[chemistryTdsHelper] Daily history ${id} could not be limited; resetting it.`);
956
+ return { samples: [], json: '[]' };
957
+ }
958
+
959
+ return { samples: limitedSamples, json };
960
+ },
961
+
962
+ _dayKey(ts) {
963
+ const date = new Date(ts);
964
+ const year = String(date.getFullYear());
965
+ const month = String(date.getMonth() + 1).padStart(2, '0');
966
+ const day = String(date.getDate()).padStart(2, '0');
967
+ return [year, month, day].join('-');
968
+ },
969
+
970
+ _dayStartTs(ts) {
971
+ const date = new Date(ts);
972
+ date.setHours(0, 0, 0, 0);
973
+ return date.getTime();
974
+ },
975
+
771
976
  async _readJsonArray(id) {
772
977
  const state = await this.adapter.getStateAsync(id);
978
+ const rawValue = state?.val;
979
+
980
+ if (rawValue === null || rawValue === undefined || rawValue === '') {
981
+ return [];
982
+ }
983
+
984
+ if (typeof rawValue !== 'string') {
985
+ this.adapter.log.warn(`[chemistryTdsHelper] Ignoring non-string history state ${id}.`);
986
+ return [];
987
+ }
988
+
989
+ if (Buffer.byteLength(rawValue, 'utf8') > MAX_HISTORY_BYTES) {
990
+ this.adapter.log.warn(`[chemistryTdsHelper] Ignoring oversized history state ${id} (> 64 KB).`);
991
+ return [];
992
+ }
773
993
 
774
994
  try {
775
- const parsed = JSON.parse(String(state?.val || '[]'));
776
- return Array.isArray(parsed) ? parsed : [];
995
+ const parsed = JSON.parse(rawValue);
996
+ if (!Array.isArray(parsed)) {
997
+ return [];
998
+ }
999
+
1000
+ const nowTs = Date.now();
1001
+ return parsed
1002
+ .map(sample => {
1003
+ const ts = Number(sample?.ts);
1004
+ const value = Number(sample?.value);
1005
+ if (!Number.isFinite(ts) || ts <= 0 || ts > nowTs) {
1006
+ return null;
1007
+ }
1008
+ if (!Number.isFinite(value) || value < 0 || value > 10000) {
1009
+ return null;
1010
+ }
1011
+ return { ts, time: this._formatDateTime(new Date(ts)), value };
1012
+ })
1013
+ .filter(Boolean)
1014
+ .sort((a, b) => a.ts - b.ts)
1015
+ .slice(-MAX_HISTORY_SAMPLES);
777
1016
  } catch {
778
1017
  return [];
779
1018
  }
780
1019
  },
781
1020
 
1021
+ _prepareHistoryForWrite(samples, id) {
1022
+ let limitedSamples = samples.slice(-MAX_HISTORY_SAMPLES);
1023
+ let json = JSON.stringify(limitedSamples);
1024
+ let trimmedForSize = false;
1025
+
1026
+ while (limitedSamples.length && Buffer.byteLength(json, 'utf8') > MAX_HISTORY_BYTES) {
1027
+ limitedSamples = limitedSamples.slice(1);
1028
+ json = JSON.stringify(limitedSamples);
1029
+ trimmedForSize = true;
1030
+ }
1031
+
1032
+ if (trimmedForSize) {
1033
+ this.adapter.log.warn(`[chemistryTdsHelper] History ${id} was trimmed to stay within 64 KB.`);
1034
+ }
1035
+
1036
+ if (Buffer.byteLength(json, 'utf8') > MAX_HISTORY_BYTES) {
1037
+ this.adapter.log.warn(`[chemistryTdsHelper] History ${id} could not be limited safely; resetting it.`);
1038
+ return { samples: [], json: '[]' };
1039
+ }
1040
+
1041
+ return { samples: limitedSamples, json };
1042
+ },
1043
+
782
1044
  async _setString(id, value) {
783
1045
  await this.adapter.setStateChangedAsync(id, { val: String(value ?? ''), ack: true });
784
1046
  },
@@ -1,5 +1,7 @@
1
1
  'use strict';
2
2
 
3
+ const MAX_DEBUG_LOG_BYTES = 64 * 1024;
4
+
3
5
  /**
4
6
  * debugLogHelper (SystemCheck-Version)
5
7
  * - Überwacht nur einen auswählbaren Bereich (z. B. pump.*, solar.*, runtime.*)
@@ -157,9 +159,9 @@ const debugLogHelper = {
157
159
  const current = (await this.adapter.getStateAsync('SystemCheck.debug_logs.log'))?.val || '';
158
160
  const newVal = current + this.buffer;
159
161
  await this.adapter.setStateAsync('SystemCheck.debug_logs.log', {
160
- val: newVal.slice(-60000),
162
+ val: this._truncateUtf8(newVal, MAX_DEBUG_LOG_BYTES),
161
163
  ack: true,
162
- }); // max ~60k Zeichen
164
+ }); // hard limit: 64 KB UTF-8
163
165
  this.buffer = '';
164
166
  if (this.bufferTimer) {
165
167
  this.adapter.clearTimeout(this.bufferTimer);
@@ -170,6 +172,19 @@ const debugLogHelper = {
170
172
  }
171
173
  },
172
174
 
175
+ _truncateUtf8(value, maxBytes) {
176
+ const buffer = Buffer.from(String(value ?? ''), 'utf8');
177
+ if (buffer.length <= maxBytes) {
178
+ return String(value ?? '');
179
+ }
180
+
181
+ let start = buffer.length - maxBytes;
182
+ while (start < buffer.length && (buffer[start] & 0xc0) === 0x80) {
183
+ start += 1;
184
+ }
185
+ return buffer.subarray(start).toString('utf8');
186
+ },
187
+
173
188
  /**
174
189
  * Löscht das Log komplett
175
190
  */
@@ -304,6 +304,9 @@ const runtimeHelper = {
304
304
  };
305
305
  const write = (id, val) => this.adapter.setStateAsync(`circulation.plausibility.${id}`, { val, ack: true });
306
306
 
307
+ const enabledState = await this.adapter.getStateAsync('circulation.plausibility.000_enabled');
308
+ const plausibilityEnabled = enabledState?.val === false ? false : true;
309
+
307
310
  const currentPower = await readNumber('pump.current_power');
308
311
  const pumpMaxWatt = await readNumber('pump.pump_max_watt');
309
312
  const pumpPowerLph = await readNumber('pump.pump_power_lph');
@@ -313,6 +316,25 @@ const runtimeHelper = {
313
316
  const previousTotal =
314
317
  this.lastPlausibilityDailyTotal === null ? Number(oldTotal) || 0 : this.lastPlausibilityDailyTotal;
315
318
 
319
+ if (!plausibilityEnabled) {
320
+ await write('00_status', 'disabled');
321
+ await write('01_level', 'info');
322
+ await write('02_message_key', 'circulation_plausibility_disabled');
323
+ await write('03_last_update', nowIso);
324
+ await write('10_power_warning', false);
325
+ await write('20_flow_warning', false);
326
+ await write('30_jump_warning', false);
327
+ await write('40_last_daily_total', previousTotal);
328
+ await write('41_current_daily_total', currentTotal);
329
+ await write('42_delta_liters', 0);
330
+ await write('43_elapsed_seconds', 0);
331
+ await write('44_max_plausible_delta_liters', 0);
332
+
333
+ this.lastPlausibilityDailyTotal = currentTotal;
334
+ this.lastPlausibilityCheckTs = nowMs;
335
+ return;
336
+ }
337
+
316
338
  const powerLimit = pumpMaxWatt > 0 ? pumpMaxWatt * 1.2 : 0;
317
339
  const flowLimit = pumpPowerLph > 0 ? pumpPowerLph * 1.2 : 0;
318
340
  let powerWarning = currentPower > 0 && powerLimit > 0 && currentPower > powerLimit;
@@ -69,6 +69,35 @@ const solarExtendedHelper = {
69
69
  const isTimePriority = activeHelper === 'timeHelper';
70
70
  const actorConfigOk = controlObjectId !== '' && (controlType === 'boolean' || controlType === 'socket');
71
71
 
72
+ const poolReferenceStateId =
73
+ poolTemperatureSource === 'ground'
74
+ ? 'temperature.ground.current'
75
+ : poolTemperatureSource === 'surface'
76
+ ? 'temperature.surface.current'
77
+ : '';
78
+ const collectorDeltaState = await this.adapter.getStateAsync('temperature.collector.current');
79
+ const poolReferenceDeltaState = poolReferenceStateId
80
+ ? await this.adapter.getStateAsync(poolReferenceStateId)
81
+ : null;
82
+ const collectorDeltaValue = collectorDeltaState?.val;
83
+ const poolReferenceDeltaValue = poolReferenceDeltaState?.val;
84
+ const collectorDeltaNumber = Number(collectorDeltaValue);
85
+ const poolReferenceDeltaNumber = Number(poolReferenceDeltaValue);
86
+ const extendedDeltaValid =
87
+ collectorDeltaValue !== null &&
88
+ collectorDeltaValue !== undefined &&
89
+ collectorDeltaValue !== '' &&
90
+ poolReferenceDeltaValue !== null &&
91
+ poolReferenceDeltaValue !== undefined &&
92
+ poolReferenceDeltaValue !== '' &&
93
+ Number.isFinite(collectorDeltaNumber) &&
94
+ Number.isFinite(poolReferenceDeltaNumber);
95
+
96
+ await this.adapter.setStateChangedAsync('solar.extended.collector_pool_reference_delta', {
97
+ val: extendedDeltaValid ? Number((collectorDeltaNumber - poolReferenceDeltaNumber).toFixed(2)) : null,
98
+ ack: true,
99
+ });
100
+
72
101
  let configOk = false;
73
102
  let requestActive = false;
74
103
  let active = false;
@@ -71,8 +71,26 @@ const solarHelper = {
71
71
  const hysteresis = (await this.adapter.getStateAsync('solar.hysteresis_active'))?.val;
72
72
 
73
73
  // Temperaturen laden
74
- const collector = Number((await this.adapter.getStateAsync('temperature.collector.current'))?.val);
75
- const pool = Number((await this.adapter.getStateAsync('temperature.surface.current'))?.val); // Oberfläche = Pooltemp
74
+ const collectorState = await this.adapter.getStateAsync('temperature.collector.current');
75
+ const poolState = await this.adapter.getStateAsync('temperature.surface.current');
76
+ const collectorValue = collectorState?.val;
77
+ const poolValue = poolState?.val;
78
+ const collector = Number(collectorValue);
79
+ const pool = Number(poolValue); // Oberfläche = Pooltemp
80
+ const solarDeltaValid =
81
+ collectorValue !== null &&
82
+ collectorValue !== undefined &&
83
+ collectorValue !== '' &&
84
+ poolValue !== null &&
85
+ poolValue !== undefined &&
86
+ poolValue !== '' &&
87
+ Number.isFinite(collector) &&
88
+ Number.isFinite(pool);
89
+
90
+ await this.adapter.setStateChangedAsync('solar.collector_surface_delta', {
91
+ val: solarDeltaValid ? Number((collector - pool).toFixed(2)) : null,
92
+ ack: true,
93
+ });
76
94
 
77
95
  // NEU: Absicherung gegen ungültige Werte
78
96
  if (isNaN(collector) || isNaN(pool)) {
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  const { I18n } = require('@iobroker/adapter-core');
4
+ const MAX_LOGBOOK_BYTES = 64 * 1024;
4
5
 
5
6
  /**
6
7
  * solarLogbookHelper
@@ -261,7 +262,10 @@ const solarLogbookHelper = {
261
262
  trimmedLog = [...dayLog, newLogItem].slice(-100);
262
263
  }
263
264
 
264
- const dayLogText = this._buildDayLogText(trimmedLog);
265
+ const limitedLog = this._limitDayLogByBytes(trimmedLog);
266
+ trimmedLog = limitedLog.entries;
267
+ const dayLogJson = limitedLog.json;
268
+ const dayLogText = limitedLog.text;
265
269
 
266
270
  await this.adapter.setStateChangedAsync('analytics.insights.solar.logbook.current_entry', {
267
271
  val: entry.text,
@@ -274,7 +278,7 @@ const solarLogbookHelper = {
274
278
  });
275
279
 
276
280
  await this.adapter.setStateChangedAsync('analytics.insights.solar.logbook.day_log_json', {
277
- val: JSON.stringify(trimmedLog),
281
+ val: dayLogJson,
278
282
  ack: true,
279
283
  });
280
284
 
@@ -518,6 +522,29 @@ const solarLogbookHelper = {
518
522
  return now.getTime() - lastTimestamp >= minIntervalMs;
519
523
  },
520
524
 
525
+ _limitDayLogByBytes(entries) {
526
+ let limitedEntries = entries.slice(-100);
527
+ let json = JSON.stringify(limitedEntries);
528
+ let text = this._buildDayLogText(limitedEntries);
529
+ let wasTrimmed = false;
530
+
531
+ while (
532
+ limitedEntries.length &&
533
+ (Buffer.byteLength(json, 'utf8') > MAX_LOGBOOK_BYTES || Buffer.byteLength(text, 'utf8') > MAX_LOGBOOK_BYTES)
534
+ ) {
535
+ limitedEntries = limitedEntries.slice(1);
536
+ json = JSON.stringify(limitedEntries);
537
+ text = this._buildDayLogText(limitedEntries);
538
+ wasTrimmed = true;
539
+ }
540
+
541
+ if (wasTrimmed) {
542
+ this.adapter.log.warn('[solarLogbookHelper] Day log was trimmed to keep JSON and text within 64 KB.');
543
+ }
544
+
545
+ return { entries: limitedEntries, json, text };
546
+ },
547
+
521
548
  _buildDayLogText(dayLog) {
522
549
  return dayLog.map(item => `${item.time} - ${item.text}`).join('\n');
523
550
  },
@@ -589,6 +616,11 @@ const solarLogbookHelper = {
589
616
  return [];
590
617
  }
591
618
 
619
+ if (Buffer.byteLength(jsonText, 'utf8') > MAX_LOGBOOK_BYTES) {
620
+ this.adapter.log.warn('[solarLogbookHelper] Oversized day_log_json was discarded before parsing.');
621
+ return [];
622
+ }
623
+
592
624
  try {
593
625
  const parsed = JSON.parse(jsonText);
594
626
  return Array.isArray(parsed) ? parsed : [];
package/lib/i18n/de.json CHANGED
@@ -380,5 +380,6 @@
380
380
  "circulation_plausibility_power_warning": "Die Pumpenleistung ist im Verhältnis zur konfigurierten Maximalleistung unplausibel hoch.",
381
381
  "circulation_plausibility_flow_warning": "Der berechnete Durchfluss ist im Verhältnis zur konfigurierten Nennförderleistung unplausibel hoch.",
382
382
  "circulation_plausibility_daily_total_jump_warning": "Das Tagesumwälzvolumen ist schneller gestiegen als physikalisch plausibel.",
383
- "circulation_plausibility_multiple_warnings": "Mehrere Plausibilitätswarnungen zur Umwälzberechnung sind aktiv."
383
+ "circulation_plausibility_multiple_warnings": "Mehrere Plausibilitätswarnungen zur Umwälzberechnung sind aktiv.",
384
+ "circulation_plausibility_disabled": "Die Plausibilitätsprüfung der Umwälzberechnung ist deaktiviert."
384
385
  }
package/lib/i18n/en.json CHANGED
@@ -407,5 +407,6 @@
407
407
  "circulation_plausibility_power_warning": "Pump power is implausibly high compared to the configured maximum power.",
408
408
  "circulation_plausibility_flow_warning": "Calculated flow rate is implausibly high compared to the configured nominal flow.",
409
409
  "circulation_plausibility_daily_total_jump_warning": "Daily circulation volume increased faster than physically plausible.",
410
- "circulation_plausibility_multiple_warnings": "Multiple circulation plausibility warnings are active."
410
+ "circulation_plausibility_multiple_warnings": "Multiple circulation plausibility warnings are active.",
411
+ "circulation_plausibility_disabled": "Circulation plausibility check is disabled."
411
412
  }
package/lib/i18n/es.json CHANGED
@@ -403,6 +403,7 @@
403
403
  "circulation_plausibility_power_warning": "La potencia de la bomba es excesiva en relación con la potencia máxima configurada.",
404
404
  "circulation_plausibility_flow_warning": "El caudal calculado es excesivo en relación con el caudal nominal configurado.",
405
405
  "circulation_plausibility_daily_total_jump_warning": "El volumen diario de circulación ha aumentado más rápido de lo físicamente plausible.",
406
- "circulation_plausibility_multiple_warnings": "Hay varias advertencias de plausibilidad de circulación activas."
406
+ "circulation_plausibility_multiple_warnings": "Hay varias advertencias de plausibilidad de circulación activas.",
407
+ "circulation_plausibility_disabled": "La comprobación de plausibilidad de la circulación está desactivada."
407
408
  }
408
409
 
package/lib/i18n/fr.json CHANGED
@@ -403,5 +403,6 @@
403
403
  "circulation_plausibility_power_warning": "La puissance de la pompe est anormalement élevée par rapport à la puissance maximale configurée.",
404
404
  "circulation_plausibility_flow_warning": "Le débit calculé est anormalement élevé par rapport au débit nominal configuré.",
405
405
  "circulation_plausibility_daily_total_jump_warning": "Le volume quotidien de circulation a augmenté plus rapidement que physiquement plausible.",
406
- "circulation_plausibility_multiple_warnings": "Plusieurs avertissements de plausibilité de circulation sont actifs."
406
+ "circulation_plausibility_multiple_warnings": "Plusieurs avertissements de plausibilité de circulation sont actifs.",
407
+ "circulation_plausibility_disabled": "La vérification de plausibilité de la circulation est désactivée."
407
408
  }
package/lib/i18n/it.json CHANGED
@@ -403,5 +403,6 @@
403
403
  "circulation_plausibility_power_warning": "La potenza della pompa è eccessivamente alta rispetto alla potenza massima configurata.",
404
404
  "circulation_plausibility_flow_warning": "La portata calcolata è eccessivamente alta rispetto alla portata nominale configurata.",
405
405
  "circulation_plausibility_daily_total_jump_warning": "Il volume giornaliero di circolazione è aumentato più velocemente di quanto fisicamente plausibile.",
406
- "circulation_plausibility_multiple_warnings": "Sono attivi più avvisi di plausibilità della circolazione."
406
+ "circulation_plausibility_multiple_warnings": "Sono attivi più avvisi di plausibilità della circolazione.",
407
+ "circulation_plausibility_disabled": "Il controllo di plausibilità della circolazione è disattivato."
407
408
  }
package/lib/i18n/nl.json CHANGED
@@ -403,5 +403,6 @@
403
403
  "circulation_plausibility_power_warning": "Het pompvermogen is onwaarschijnlijk hoog ten opzichte van het ingestelde maximale vermogen.",
404
404
  "circulation_plausibility_flow_warning": "Het berekende debiet is onwaarschijnlijk hoog ten opzichte van het ingestelde nominale debiet.",
405
405
  "circulation_plausibility_daily_total_jump_warning": "Het dagelijkse circulatievolume is sneller gestegen dan fysiek plausibel.",
406
- "circulation_plausibility_multiple_warnings": "Er zijn meerdere waarschuwingen voor circulatie-plausibiliteit actief."
406
+ "circulation_plausibility_multiple_warnings": "Er zijn meerdere waarschuwingen voor circulatie-plausibiliteit actief.",
407
+ "circulation_plausibility_disabled": "De plausibiliteitscontrole van de circulatie is uitgeschakeld."
407
408
  }
package/lib/i18n/pl.json CHANGED
@@ -403,5 +403,6 @@
403
403
  "circulation_plausibility_power_warning": "Moc pompy jest nienaturalnie wysoka względem skonfigurowanej mocy maksymalnej.",
404
404
  "circulation_plausibility_flow_warning": "Obliczony przepływ jest nienaturalnie wysoki względem skonfigurowanego przepływu nominalnego.",
405
405
  "circulation_plausibility_daily_total_jump_warning": "Dzienna objętość obiegu wzrosła szybciej niż jest to fizycznie możliwe.",
406
- "circulation_plausibility_multiple_warnings": "Aktywnych jest kilka ostrzeżeń dotyczących wiarygodności obiegu."
406
+ "circulation_plausibility_multiple_warnings": "Aktywnych jest kilka ostrzeżeń dotyczących wiarygodności obiegu.",
407
+ "circulation_plausibility_disabled": "Kontrola wiarygodności obiegu jest wyłączona."
407
408
  }