iobroker.poolcontrol 1.3.33 → 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
  */
@@ -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 : [];
@@ -897,8 +897,25 @@ async function createChemistryOrpStates(adapter) {
897
897
  de: 'ORP-Historienwerte',
898
898
  },
899
899
  desc: {
900
- en: 'Internal list of valid ORP measurement samples for trend calculation.',
901
- de: 'Interne Liste gültiger ORP-Messwerte für die Trendberechnung.',
900
+ en: 'Bounded short-term JSON history of valid ORP samples: up to 7 days, 672 samples, and 64 KB.',
901
+ de: 'Begrenzte JSON-Kurzzeithistorie gültiger ORP-Messwerte: bis zu 7 Tage, 672 Samples und 64 KB.',
902
+ },
903
+ type: 'string',
904
+ role: 'json',
905
+ read: true,
906
+ write: false,
907
+ def: '[]',
908
+ persist: true,
909
+ });
910
+
911
+ await createState(adapter, 'chemistry.orp.history.daily_json', {
912
+ name: {
913
+ en: 'ORP daily reference history',
914
+ de: 'ORP-Tagesreferenzhistorie',
915
+ },
916
+ desc: {
917
+ en: 'Internal bounded local-calendar-day aggregates (min/max/avg/last/count) for 30-day trend calculations; last is used as reference.',
918
+ de: 'Interne begrenzte Aggregate lokaler Kalendertage (min/max/avg/last/count) für 30-Tage-Trendberechnungen; last dient als Referenz.',
902
919
  },
903
920
  type: 'string',
904
921
  role: 'json',
@@ -665,8 +665,25 @@ async function createChemistryPhStates(adapter) {
665
665
  de: 'Messwerte JSON',
666
666
  },
667
667
  desc: {
668
- en: 'Internal JSON list of valid pH samples for up to 30 days.',
669
- de: 'Interne JSON-Liste gültiger pH-Messpunkte für bis zu 30 Tage.',
668
+ en: 'Bounded short-term JSON history of valid pH samples: up to 7 days, 672 samples, and 64 KB.',
669
+ de: 'Begrenzte JSON-Kurzzeithistorie gültiger pH-Messpunkte: bis zu 7 Tage, 672 Samples und 64 KB.',
670
+ },
671
+ type: 'string',
672
+ role: 'json',
673
+ read: true,
674
+ write: false,
675
+ def: '[]',
676
+ persist: true,
677
+ });
678
+
679
+ await createState(adapter, 'chemistry.ph.history.daily_json', {
680
+ name: {
681
+ en: 'pH daily reference history',
682
+ de: 'pH-Tagesreferenzhistorie',
683
+ },
684
+ desc: {
685
+ en: 'Internal bounded local-calendar-day aggregates (min/max/avg/last/count) for 30-day trend calculations; last is used as reference.',
686
+ de: 'Interne begrenzte Aggregate lokaler Kalendertage (min/max/avg/last/count) für 30-Tage-Trendberechnungen; last dient als Referenz.',
670
687
  },
671
688
  type: 'string',
672
689
  role: 'json',
@@ -734,8 +734,25 @@ async function createChemistryTdsStates(adapter) {
734
734
  de: 'Messwerte JSON',
735
735
  },
736
736
  desc: {
737
- en: 'Internal JSON list of valid TDS samples for up to 30 days.',
738
- de: 'Interne JSON-Liste gültiger TDS-Messpunkte für bis zu 30 Tage.',
737
+ en: 'Bounded short-term JSON history of valid TDS samples: up to 7 days, 672 samples, and 64 KB.',
738
+ de: 'Begrenzte JSON-Kurzzeithistorie gültiger TDS-Messpunkte: bis zu 7 Tage, 672 Samples und 64 KB.',
739
+ },
740
+ type: 'string',
741
+ role: 'json',
742
+ read: true,
743
+ write: false,
744
+ def: '[]',
745
+ persist: true,
746
+ });
747
+
748
+ await createState(adapter, 'chemistry.tds.history.daily_json', {
749
+ name: {
750
+ en: 'TDS daily reference history',
751
+ de: 'TDS-Tagesreferenzhistorie',
752
+ },
753
+ desc: {
754
+ en: 'Internal bounded local-calendar-day aggregates (min/max/avg/last/count) for 30-day trend calculations; last is used as reference.',
755
+ de: 'Interne begrenzte Aggregate lokaler Kalendertage (min/max/avg/last/count) für 30-Tage-Trendberechnungen; last dient als Referenz.',
739
756
  },
740
757
  type: 'string',
741
758
  role: 'json',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "iobroker.poolcontrol",
3
- "version": "1.3.33",
3
+ "version": "1.3.34",
4
4
  "description": "Steuerung & Automatisierung für den Pool (Pumpe, Heizung, Ventile, Sensoren).",
5
5
  "author": "DasBo1975 <dasbo1975@outlook.de>",
6
6
  "homepage": "https://github.com/DasBo1975/ioBroker.poolcontrol",
@@ -21,7 +21,7 @@
21
21
  "node": ">= 22"
22
22
  },
23
23
  "dependencies": {
24
- "@iobroker/adapter-core": "^3.3.2"
24
+ "@iobroker/adapter-core": "^3.4.1"
25
25
  },
26
26
  "devDependencies": {
27
27
  "@alcalzone/release-script": "^5.2.1",