iobroker.poolcontrol 1.3.33 → 1.3.35
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -14
- package/admin/i18n/uk.json +5 -5
- package/io-package.json +27 -27
- package/lib/helpers/chemistryOrpHelper.js +279 -17
- package/lib/helpers/chemistryPhHelper.js +279 -17
- package/lib/helpers/chemistryTdsHelper.js +279 -17
- package/lib/helpers/debugLogHelper.js +17 -2
- package/lib/helpers/runtimeHelper.js +12 -9
- package/lib/helpers/solarLogbookHelper.js +34 -2
- package/lib/stateDefinitions/chemistryOrpStates.js +19 -2
- package/lib/stateDefinitions/chemistryPhStates.js +19 -2
- package/lib/stateDefinitions/chemistryTdsStates.js +19 -2
- package/package.json +2 -2
|
@@ -2,8 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
const { I18n } = require('@iobroker/adapter-core');
|
|
4
4
|
|
|
5
|
-
const MAX_HISTORY_AGE_MS =
|
|
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,
|
|
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
|
|
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 &&
|
|
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',
|
|
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
|
-
|
|
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(
|
|
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(
|
|
776
|
-
|
|
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:
|
|
162
|
+
val: this._truncateUtf8(newVal, MAX_DEBUG_LOG_BYTES),
|
|
161
163
|
ack: true,
|
|
162
|
-
}); //
|
|
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
|
*/
|
|
@@ -230,20 +230,23 @@ const runtimeHelper = {
|
|
|
230
230
|
const poolSize = (await this.adapter.getStateAsync('general.pool_size'))?.val || 0;
|
|
231
231
|
const minCirc = (await this.adapter.getStateAsync('general.min_circulation_per_day'))?.val || 1;
|
|
232
232
|
|
|
233
|
-
//
|
|
233
|
+
// Soll- und Restmenge immer direkt setzen – auch ohne Durchfluss
|
|
234
234
|
const dailyRequired = Math.round(poolSize * minCirc);
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
235
|
+
|
|
236
|
+
// Bestehenden Tageswert laden, damit daily_remaining unabhängig vom Live-Durchfluss konsistent bleibt
|
|
237
|
+
const oldTotal = Number((await this.adapter.getStateAsync('circulation.daily_total'))?.val || 0);
|
|
238
|
+
const remainingFromCurrentTotal = Math.max(dailyRequired - oldTotal, 0);
|
|
239
|
+
|
|
240
|
+
await this.adapter.setStateAsync('circulation.daily_required', { val: dailyRequired, ack: true });
|
|
241
|
+
await this.adapter.setStateAsync('circulation.daily_remaining', {
|
|
242
|
+
val: remainingFromCurrentTotal,
|
|
243
|
+
ack: true,
|
|
244
|
+
});
|
|
238
245
|
|
|
239
246
|
// Umwälzmenge berechnen
|
|
240
247
|
// Reeller Durchflusswert aus pump.live.flow_current_lh
|
|
241
248
|
const liveFlowLh = Number((await this.adapter.getStateAsync('pump.live.flow_current_lh'))?.val || 0);
|
|
242
249
|
|
|
243
|
-
// Bestehende Werte für Total/Remaining laden
|
|
244
|
-
const oldTotal = Number((await this.adapter.getStateAsync('circulation.daily_total'))?.val || 0);
|
|
245
|
-
const oldRemaining = Number((await this.adapter.getStateAsync('circulation.daily_remaining'))?.val || 0);
|
|
246
|
-
|
|
247
250
|
if (liveFlowLh <= 0) {
|
|
248
251
|
await this._updateCirculationPlausibility({
|
|
249
252
|
dailyTotal: oldTotal,
|
|
@@ -279,7 +282,7 @@ const runtimeHelper = {
|
|
|
279
282
|
);
|
|
280
283
|
} else {
|
|
281
284
|
this.adapter.log.debug(
|
|
282
|
-
`[runtimeHelper] No valid live data – keeping existing
|
|
285
|
+
`[runtimeHelper] No valid live data – keeping existing total (Total=${oldTotal}, Required=${dailyRequired}, Remaining=${remainingFromCurrentTotal})`,
|
|
283
286
|
);
|
|
284
287
|
}
|
|
285
288
|
} catch (err) {
|
|
@@ -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
|
|
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:
|
|
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: '
|
|
901
|
-
de: '
|
|
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: '
|
|
669
|
-
de: '
|
|
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: '
|
|
738
|
-
de: '
|
|
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.
|
|
3
|
+
"version": "1.3.35",
|
|
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.
|
|
24
|
+
"@iobroker/adapter-core": "^3.4.1"
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|
|
27
27
|
"@alcalzone/release-script": "^5.2.1",
|