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.
- package/README.md +28 -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 +22 -0
- package/lib/helpers/solarExtendedHelper.js +29 -0
- package/lib/helpers/solarHelper.js +20 -2
- package/lib/helpers/solarLogbookHelper.js +34 -2
- package/lib/i18n/de.json +2 -1
- package/lib/i18n/en.json +2 -1
- package/lib/i18n/es.json +2 -1
- package/lib/i18n/fr.json +2 -1
- package/lib/i18n/it.json +2 -1
- package/lib/i18n/nl.json +2 -1
- package/lib/i18n/pl.json +2 -1
- package/lib/i18n/pt.json +2 -1
- package/lib/i18n/ru.json +2 -1
- package/lib/i18n/uk.json +2 -1
- package/lib/i18n/zh-cn.json +2 -1
- package/lib/stateDefinitions/chemistryOrpStates.js +19 -2
- package/lib/stateDefinitions/chemistryPhStates.js +19 -2
- package/lib/stateDefinitions/chemistryTdsStates.js +19 -2
- package/lib/stateDefinitions/runtimeStates.js +13 -1
- package/lib/stateDefinitions/solarExtendedStates.js +22 -0
- package/lib/stateDefinitions/solarStates.js +23 -0
- 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
|
*/
|
|
@@ -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
|
|
75
|
-
const
|
|
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
|
|
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 : [];
|
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
|
}
|