iobroker.utility-monitor 1.5.0 → 1.6.1
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 +86 -8
- package/io-package.json +31 -53
- package/lib/billingManager.js +333 -49
- package/lib/calculator.js +27 -13
- package/lib/multiMeterManager.js +192 -2
- package/lib/state/history.js +95 -0
- package/lib/state/meter.js +605 -0
- package/lib/state/roles.js +16 -0
- package/lib/state/totals.js +136 -0
- package/lib/state/utility.js +650 -0
- package/lib/stateManager.js +8 -2046
- package/lib/utils/helpers.js +56 -0
- package/lib/utils/stateCache.js +147 -0
- package/main.js +2 -6
- package/package.json +1 -1
package/lib/billingManager.js
CHANGED
|
@@ -171,7 +171,7 @@ class BillingManager {
|
|
|
171
171
|
}
|
|
172
172
|
|
|
173
173
|
if (!startDate || isNaN(startDate.getTime())) {
|
|
174
|
-
const lastYearStart = await this.adapter.getStateAsync(`${type}.statistics.lastYearStart`);
|
|
174
|
+
const lastYearStart = await this.adapter.getStateAsync(`${type}.statistics.timestamps.lastYearStart`);
|
|
175
175
|
startDate = new Date(lastYearStart?.val || Date.now());
|
|
176
176
|
}
|
|
177
177
|
|
|
@@ -372,7 +372,11 @@ class BillingManager {
|
|
|
372
372
|
// even if the user closes the period early (e.g. 2 days before)
|
|
373
373
|
const thisYearAnniversary = new Date(startDate);
|
|
374
374
|
thisYearAnniversary.setFullYear(new Date().getFullYear());
|
|
375
|
-
await this.adapter.setStateAsync(
|
|
375
|
+
await this.adapter.setStateAsync(
|
|
376
|
+
`${type}.statistics.timestamps.lastYearStart`,
|
|
377
|
+
thisYearAnniversary.getTime(),
|
|
378
|
+
true,
|
|
379
|
+
);
|
|
376
380
|
|
|
377
381
|
this.adapter.log.info(`✅ Abrechnungszeitraum ${year} für ${type} erfolgreich abgeschlossen!`);
|
|
378
382
|
this.adapter.log.info(
|
|
@@ -419,11 +423,96 @@ class BillingManager {
|
|
|
419
423
|
return;
|
|
420
424
|
}
|
|
421
425
|
|
|
426
|
+
// Archive data for this meter
|
|
422
427
|
const year = startDate.getFullYear();
|
|
423
428
|
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
//
|
|
429
|
+
this.adapter.log.info(`Archiving data for ${basePath} year ${year}...`);
|
|
430
|
+
|
|
431
|
+
// Create history structure for this meter
|
|
432
|
+
await this.adapter.setObjectNotExistsAsync(`${basePath}.history`, {
|
|
433
|
+
type: 'channel',
|
|
434
|
+
common: { name: 'Historie' },
|
|
435
|
+
native: {},
|
|
436
|
+
});
|
|
437
|
+
await this.adapter.setObjectNotExistsAsync(`${basePath}.history.${year}`, {
|
|
438
|
+
type: 'channel',
|
|
439
|
+
common: { name: `Jahr ${year}` },
|
|
440
|
+
native: {},
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
// Get current values for archiving
|
|
444
|
+
const yearlyState = await this.adapter.getStateAsync(`${basePath}.consumption.yearly`);
|
|
445
|
+
const totalYearlyState = await this.adapter.getStateAsync(`${basePath}.costs.totalYearly`);
|
|
446
|
+
const balanceState = await this.adapter.getStateAsync(`${basePath}.costs.balance`);
|
|
447
|
+
|
|
448
|
+
const yearly = yearlyState?.val || 0;
|
|
449
|
+
const totalYearly = totalYearlyState?.val || 0;
|
|
450
|
+
const balance = balanceState?.val || 0;
|
|
451
|
+
|
|
452
|
+
const consumptionUnit = type === 'gas' ? 'kWh' : type === 'water' ? 'm³' : 'kWh';
|
|
453
|
+
|
|
454
|
+
// Archive yearly consumption
|
|
455
|
+
await this.adapter.setObjectNotExistsAsync(`${basePath}.history.${year}.yearly`, {
|
|
456
|
+
type: 'state',
|
|
457
|
+
common: {
|
|
458
|
+
name: `Jahresverbrauch ${year}`,
|
|
459
|
+
type: 'number',
|
|
460
|
+
role: 'value',
|
|
461
|
+
read: true,
|
|
462
|
+
write: false,
|
|
463
|
+
unit: consumptionUnit,
|
|
464
|
+
},
|
|
465
|
+
native: {},
|
|
466
|
+
});
|
|
467
|
+
await this.adapter.setStateAsync(`${basePath}.history.${year}.yearly`, yearly, true);
|
|
468
|
+
|
|
469
|
+
// Archive gas volume if applicable
|
|
470
|
+
if (type === 'gas') {
|
|
471
|
+
const yearlyVolume = (await this.adapter.getStateAsync(`${basePath}.consumption.yearlyVolume`))?.val || 0;
|
|
472
|
+
await this.adapter.setObjectNotExistsAsync(`${basePath}.history.${year}.yearlyVolume`, {
|
|
473
|
+
type: 'state',
|
|
474
|
+
common: {
|
|
475
|
+
name: `Jahresverbrauch ${year} (m³)`,
|
|
476
|
+
type: 'number',
|
|
477
|
+
role: 'value',
|
|
478
|
+
read: true,
|
|
479
|
+
write: false,
|
|
480
|
+
unit: 'm³',
|
|
481
|
+
},
|
|
482
|
+
native: {},
|
|
483
|
+
});
|
|
484
|
+
await this.adapter.setStateAsync(`${basePath}.history.${year}.yearlyVolume`, yearlyVolume, true);
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// Archive total yearly costs
|
|
488
|
+
await this.adapter.setObjectNotExistsAsync(`${basePath}.history.${year}.totalYearly`, {
|
|
489
|
+
type: 'state',
|
|
490
|
+
common: {
|
|
491
|
+
name: `Gesamtkosten ${year}`,
|
|
492
|
+
type: 'number',
|
|
493
|
+
role: 'value.money',
|
|
494
|
+
read: true,
|
|
495
|
+
write: false,
|
|
496
|
+
unit: '€',
|
|
497
|
+
},
|
|
498
|
+
native: {},
|
|
499
|
+
});
|
|
500
|
+
await this.adapter.setStateAsync(`${basePath}.history.${year}.totalYearly`, totalYearly, true);
|
|
501
|
+
|
|
502
|
+
// Archive balance
|
|
503
|
+
await this.adapter.setObjectNotExistsAsync(`${basePath}.history.${year}.balance`, {
|
|
504
|
+
type: 'state',
|
|
505
|
+
common: {
|
|
506
|
+
name: `Bilanz ${year}`,
|
|
507
|
+
type: 'number',
|
|
508
|
+
role: 'value.money',
|
|
509
|
+
read: true,
|
|
510
|
+
write: false,
|
|
511
|
+
unit: '€',
|
|
512
|
+
},
|
|
513
|
+
native: {},
|
|
514
|
+
});
|
|
515
|
+
await this.adapter.setStateAsync(`${basePath}.history.${year}.balance`, balance, true);
|
|
427
516
|
|
|
428
517
|
// Reset consumption and costs for this meter
|
|
429
518
|
await this.adapter.setStateAsync(`${basePath}.consumption.yearly`, 0, true);
|
|
@@ -441,7 +530,11 @@ class BillingManager {
|
|
|
441
530
|
// Update lastYearStart to contract anniversary
|
|
442
531
|
const thisYearAnniversary = new Date(startDate);
|
|
443
532
|
thisYearAnniversary.setFullYear(new Date().getFullYear());
|
|
444
|
-
await this.adapter.setStateAsync(
|
|
533
|
+
await this.adapter.setStateAsync(
|
|
534
|
+
`${basePath}.statistics.timestamps.lastYearStart`,
|
|
535
|
+
thisYearAnniversary.getTime(),
|
|
536
|
+
true,
|
|
537
|
+
);
|
|
445
538
|
|
|
446
539
|
// Update totals if multiple meters exist
|
|
447
540
|
const meters = this.adapter.multiMeterManager?.getMetersForType(type) || [];
|
|
@@ -539,64 +632,100 @@ class BillingManager {
|
|
|
539
632
|
// This ensures History adapter sees clean day boundaries
|
|
540
633
|
const isResetTime = nowDate.getHours() === 23 && nowDate.getMinutes() === 59;
|
|
541
634
|
|
|
542
|
-
// Helper:
|
|
635
|
+
// Helper: Create normalized timestamp for 23:59:00 of a given date
|
|
636
|
+
// This ensures consistent timestamps regardless of actual execution time
|
|
637
|
+
const getNormalizedResetTime = date => {
|
|
638
|
+
return new Date(date.getFullYear(), date.getMonth(), date.getDate(), 23, 59, 0, 0).getTime();
|
|
639
|
+
};
|
|
640
|
+
|
|
641
|
+
// Helper: Check if timestamp is from a valid 23:59 reset today
|
|
642
|
+
// A valid reset must be from today AND from the 23:xx hour
|
|
643
|
+
// This prevents adapter restarts (e.g. at 00:01:03) from blocking the real 23:59 reset
|
|
543
644
|
const todayStart = new Date(nowDate.getFullYear(), nowDate.getMonth(), nowDate.getDate()).getTime();
|
|
544
|
-
const
|
|
645
|
+
const isValidResetToday = timestamp => {
|
|
646
|
+
if (timestamp < todayStart) {
|
|
647
|
+
return false; // Not from today
|
|
648
|
+
}
|
|
649
|
+
// Check if the timestamp was from the 23:xx hour (valid reset time)
|
|
650
|
+
const resetDate = new Date(timestamp);
|
|
651
|
+
return resetDate.getHours() === 23;
|
|
652
|
+
};
|
|
545
653
|
|
|
546
654
|
// DAILY RESET: Trigger at 23:59 if today's reset hasn't happened yet
|
|
547
|
-
const lastDayStart = await this.adapter.getStateAsync(`${basePath}.statistics.lastDayStart`);
|
|
655
|
+
const lastDayStart = await this.adapter.getStateAsync(`${basePath}.statistics.timestamps.lastDayStart`);
|
|
548
656
|
if (lastDayStart?.val) {
|
|
549
657
|
const lastResetTime = lastDayStart.val;
|
|
550
|
-
const alreadyResetToday =
|
|
658
|
+
const alreadyResetToday = isValidResetToday(lastResetTime);
|
|
551
659
|
|
|
552
660
|
// Reset at 23:59 if not yet reset today, OR catch up if we missed it (e.g. adapter was offline)
|
|
553
661
|
if (isResetTime && !alreadyResetToday) {
|
|
554
662
|
this.adapter.log.info(`Täglicher Reset für ${type} um 23:59`);
|
|
555
|
-
await this.resetDailyCounters(type);
|
|
663
|
+
await this.resetDailyCounters(type, getNormalizedResetTime(nowDate));
|
|
556
664
|
} else if (!alreadyResetToday && nowDate.getTime() > lastResetTime + 24 * 60 * 60 * 1000) {
|
|
557
665
|
// Catch-up: More than 24h since last reset (adapter was offline)
|
|
666
|
+
// Calculate when the reset SHOULD have happened (yesterday 23:59)
|
|
667
|
+
const yesterday = new Date(nowDate);
|
|
668
|
+
yesterday.setDate(yesterday.getDate() - 1);
|
|
669
|
+
const catchUpTime = getNormalizedResetTime(yesterday);
|
|
558
670
|
this.adapter.log.info(`Täglicher Reset für ${type} (Nachholung - Adapter war offline)`);
|
|
559
|
-
await this.resetDailyCounters(type);
|
|
671
|
+
await this.resetDailyCounters(type, catchUpTime);
|
|
560
672
|
}
|
|
561
673
|
}
|
|
562
674
|
|
|
563
675
|
// WEEKLY RESET: Trigger at 23:59 on Sunday if this week's reset hasn't happened yet
|
|
564
|
-
const lastWeekStart = await this.adapter.getStateAsync(`${basePath}.statistics.lastWeekStart`);
|
|
676
|
+
const lastWeekStart = await this.adapter.getStateAsync(`${basePath}.statistics.timestamps.lastWeekStart`);
|
|
565
677
|
if (lastWeekStart?.val) {
|
|
566
678
|
const lastWeekTime = lastWeekStart.val;
|
|
567
679
|
const isSunday = nowDate.getDay() === 0; // 0 = Sunday
|
|
568
680
|
|
|
569
681
|
// Check if we're in a new week (more than 6 days since last reset)
|
|
682
|
+
// BUT also check if the last reset was actually a valid 23:59 reset
|
|
683
|
+
// to prevent adapter restarts from blocking the real weekly reset
|
|
570
684
|
const daysSinceLastReset = (nowDate.getTime() - lastWeekTime) / (24 * 60 * 60 * 1000);
|
|
571
|
-
const
|
|
685
|
+
const lastWeekResetDate = new Date(lastWeekTime);
|
|
686
|
+
const wasValidWeeklyReset = lastWeekResetDate.getHours() === 23;
|
|
687
|
+
const needsWeeklyReset = daysSinceLastReset >= 6 || (isSunday && !wasValidWeeklyReset);
|
|
572
688
|
|
|
573
689
|
if (isSunday && isResetTime && needsWeeklyReset) {
|
|
574
690
|
this.adapter.log.info(`Wöchentlicher Reset für ${type} um 23:59`);
|
|
575
|
-
await this.resetWeeklyCounters(type);
|
|
576
|
-
} else if (needsWeeklyReset && daysSinceLastReset
|
|
577
|
-
// Catch-up:
|
|
691
|
+
await this.resetWeeklyCounters(type, getNormalizedResetTime(nowDate));
|
|
692
|
+
} else if (needsWeeklyReset && daysSinceLastReset >= 7) {
|
|
693
|
+
// Catch-up: 7 or more days since last reset (was offline or missed window)
|
|
694
|
+
// Calculate when the reset SHOULD have happened (last Sunday 23:59)
|
|
695
|
+
const lastSunday = new Date(nowDate);
|
|
696
|
+
lastSunday.setDate(lastSunday.getDate() - lastSunday.getDay()); // Go back to Sunday
|
|
697
|
+
const catchUpTime = getNormalizedResetTime(lastSunday);
|
|
578
698
|
this.adapter.log.info(`Wöchentlicher Reset für ${type} (Nachholung)`);
|
|
579
|
-
await this.resetWeeklyCounters(type);
|
|
699
|
+
await this.resetWeeklyCounters(type, catchUpTime);
|
|
580
700
|
}
|
|
581
701
|
}
|
|
582
702
|
|
|
583
703
|
// MONTHLY RESET: Trigger at 23:59 on last day of month
|
|
584
704
|
if (meters.length > 0) {
|
|
585
|
-
const lastMonthStartState = await this.adapter.getStateAsync(
|
|
705
|
+
const lastMonthStartState = await this.adapter.getStateAsync(
|
|
706
|
+
`${basePath}.statistics.timestamps.lastMonthStart`,
|
|
707
|
+
);
|
|
586
708
|
if (lastMonthStartState?.val) {
|
|
587
709
|
const lastMonthTime = lastMonthStartState.val;
|
|
588
710
|
const lastMonthDate = new Date(lastMonthTime);
|
|
589
711
|
const isLastDayOfMonth =
|
|
590
712
|
new Date(nowDate.getFullYear(), nowDate.getMonth() + 1, 0).getDate() === nowDate.getDate();
|
|
591
713
|
const monthChanged = nowDate.getMonth() !== lastMonthDate.getMonth();
|
|
714
|
+
// Also check if the last reset was a valid 23:59 reset
|
|
715
|
+
// to prevent adapter restarts from blocking the real monthly reset
|
|
716
|
+
const wasValidMonthlyReset = lastMonthDate.getHours() === 23;
|
|
717
|
+
const needsMonthlyReset = monthChanged || (isLastDayOfMonth && !wasValidMonthlyReset);
|
|
592
718
|
|
|
593
|
-
if (isLastDayOfMonth && isResetTime &&
|
|
719
|
+
if (isLastDayOfMonth && isResetTime && needsMonthlyReset) {
|
|
594
720
|
this.adapter.log.info(`Monatlicher Reset für ${type} um 23:59`);
|
|
595
|
-
await this.resetMonthlyCounters(type);
|
|
721
|
+
await this.resetMonthlyCounters(type, getNormalizedResetTime(nowDate));
|
|
596
722
|
} else if (monthChanged && nowDate.getDate() > 1) {
|
|
597
723
|
// Catch-up: We're past the 1st of a new month and haven't reset yet
|
|
724
|
+
// Calculate when the reset SHOULD have happened (last day of previous month 23:59)
|
|
725
|
+
const lastDayPrevMonth = new Date(nowDate.getFullYear(), nowDate.getMonth(), 0);
|
|
726
|
+
const catchUpTime = getNormalizedResetTime(lastDayPrevMonth);
|
|
598
727
|
this.adapter.log.info(`Monatlicher Reset für ${type} (Nachholung)`);
|
|
599
|
-
await this.resetMonthlyCounters(type);
|
|
728
|
+
await this.resetMonthlyCounters(type, catchUpTime);
|
|
600
729
|
}
|
|
601
730
|
}
|
|
602
731
|
}
|
|
@@ -606,7 +735,7 @@ class BillingManager {
|
|
|
606
735
|
for (const meter of meters) {
|
|
607
736
|
const meterBasePath = `${type}.${meter.name}`;
|
|
608
737
|
const lastYearStartState = await this.adapter.getStateAsync(
|
|
609
|
-
`${meterBasePath}.statistics.lastYearStart`,
|
|
738
|
+
`${meterBasePath}.statistics.timestamps.lastYearStart`,
|
|
610
739
|
);
|
|
611
740
|
|
|
612
741
|
if (lastYearStartState?.val) {
|
|
@@ -638,17 +767,19 @@ class BillingManager {
|
|
|
638
767
|
this.adapter.log.info(
|
|
639
768
|
`Yearly reset for ${meterBasePath} um 23:59 (Vertragsjubiläum morgen: ${contractStartDate})`,
|
|
640
769
|
);
|
|
641
|
-
await this.resetYearlyCountersForMeter(type, meter);
|
|
770
|
+
await this.resetYearlyCountersForMeter(type, meter, getNormalizedResetTime(nowDate));
|
|
642
771
|
|
|
643
772
|
if (meters.length > 1) {
|
|
644
773
|
await this.adapter.multiMeterManager.updateTotalCosts(type);
|
|
645
774
|
}
|
|
646
775
|
} else if (isPastAnniversary && needsReset) {
|
|
647
776
|
// Catch-up: Anniversary has passed but we haven't reset yet
|
|
777
|
+
// Use the day before anniversary 23:59 as the reset time
|
|
778
|
+
const catchUpTime = getNormalizedResetTime(dayBeforeAnniversary);
|
|
648
779
|
this.adapter.log.info(
|
|
649
780
|
`Yearly reset for ${meterBasePath} (Nachholung - Jubiläum: ${contractStartDate})`,
|
|
650
781
|
);
|
|
651
|
-
await this.resetYearlyCountersForMeter(type, meter);
|
|
782
|
+
await this.resetYearlyCountersForMeter(type, meter, catchUpTime);
|
|
652
783
|
|
|
653
784
|
if (meters.length > 1) {
|
|
654
785
|
await this.adapter.multiMeterManager.updateTotalCosts(type);
|
|
@@ -663,15 +794,18 @@ class BillingManager {
|
|
|
663
794
|
if (isDecember31 && isResetTime && !needsReset) {
|
|
664
795
|
// Reset at 23:59 on Dec 31 for the upcoming year
|
|
665
796
|
this.adapter.log.info(`Yearly reset for ${meterBasePath} um 23:59 (Kalenderjahr)`);
|
|
666
|
-
await this.resetYearlyCountersForMeter(type, meter);
|
|
797
|
+
await this.resetYearlyCountersForMeter(type, meter, getNormalizedResetTime(nowDate));
|
|
667
798
|
|
|
668
799
|
if (meters.length > 1) {
|
|
669
800
|
await this.adapter.multiMeterManager.updateTotalCosts(type);
|
|
670
801
|
}
|
|
671
802
|
} else if (needsReset) {
|
|
672
803
|
// Catch-up: We're in a new year but haven't reset yet
|
|
804
|
+
// Use Dec 31 of last year 23:59 as reset time
|
|
805
|
+
const dec31LastYear = new Date(nowDate.getFullYear() - 1, 11, 31);
|
|
806
|
+
const catchUpTime = getNormalizedResetTime(dec31LastYear);
|
|
673
807
|
this.adapter.log.info(`Yearly reset for ${meterBasePath} (Nachholung - Kalenderjahr)`);
|
|
674
|
-
await this.resetYearlyCountersForMeter(type, meter);
|
|
808
|
+
await this.resetYearlyCountersForMeter(type, meter, catchUpTime);
|
|
675
809
|
|
|
676
810
|
if (meters.length > 1) {
|
|
677
811
|
await this.adapter.multiMeterManager.updateTotalCosts(type);
|
|
@@ -687,8 +821,9 @@ class BillingManager {
|
|
|
687
821
|
* Resets daily counters
|
|
688
822
|
*
|
|
689
823
|
* @param {string} type - Utility type
|
|
824
|
+
* @param {number} [resetTimestamp] - Optional normalized timestamp (defaults to Date.now())
|
|
690
825
|
*/
|
|
691
|
-
async resetDailyCounters(type) {
|
|
826
|
+
async resetDailyCounters(type, resetTimestamp) {
|
|
692
827
|
this.adapter.log.info(`Resetting daily counters for ${type}`);
|
|
693
828
|
|
|
694
829
|
// Get all meters for this type (main + additional meters)
|
|
@@ -710,7 +845,12 @@ class BillingManager {
|
|
|
710
845
|
const dailyValue = dailyState?.val || 0;
|
|
711
846
|
|
|
712
847
|
// Save last day consumption
|
|
713
|
-
await this.adapter.setStateAsync(`${basePath}.statistics.lastDay`, dailyValue, true);
|
|
848
|
+
await this.adapter.setStateAsync(`${basePath}.statistics.consumption.lastDay`, dailyValue, true);
|
|
849
|
+
|
|
850
|
+
// Save last day costs
|
|
851
|
+
const dailyCostState = await this.adapter.getStateAsync(`${basePath}.costs.daily`);
|
|
852
|
+
const dailyCostValue = dailyCostState?.val || 0;
|
|
853
|
+
await this.adapter.setStateAsync(`${basePath}.statistics.cost.lastDay`, dailyCostValue, true);
|
|
714
854
|
|
|
715
855
|
await this.adapter.setStateAsync(`${basePath}.consumption.daily`, 0, true);
|
|
716
856
|
|
|
@@ -718,7 +858,11 @@ class BillingManager {
|
|
|
718
858
|
const dailyVolume = await this.adapter.getStateAsync(`${basePath}.consumption.dailyVolume`);
|
|
719
859
|
const dailyVolumeValue = dailyVolume?.val || 0;
|
|
720
860
|
// Save last day volume for gas
|
|
721
|
-
await this.adapter.setStateAsync(
|
|
861
|
+
await this.adapter.setStateAsync(
|
|
862
|
+
`${basePath}.statistics.consumption.lastDayVolume`,
|
|
863
|
+
dailyVolumeValue,
|
|
864
|
+
true,
|
|
865
|
+
);
|
|
722
866
|
await this.adapter.setStateAsync(`${basePath}.consumption.dailyVolume`, 0, true);
|
|
723
867
|
}
|
|
724
868
|
|
|
@@ -728,22 +872,44 @@ class BillingManager {
|
|
|
728
872
|
if (htNtEnabled) {
|
|
729
873
|
const dailyHT = await this.adapter.getStateAsync(`${basePath}.consumption.dailyHT`);
|
|
730
874
|
const dailyNT = await this.adapter.getStateAsync(`${basePath}.consumption.dailyNT`);
|
|
731
|
-
await this.adapter.setStateAsync(
|
|
732
|
-
|
|
875
|
+
await this.adapter.setStateAsync(
|
|
876
|
+
`${basePath}.statistics.consumption.lastDayHT`,
|
|
877
|
+
dailyHT?.val || 0,
|
|
878
|
+
true,
|
|
879
|
+
);
|
|
880
|
+
await this.adapter.setStateAsync(
|
|
881
|
+
`${basePath}.statistics.consumption.lastDayNT`,
|
|
882
|
+
dailyNT?.val || 0,
|
|
883
|
+
true,
|
|
884
|
+
);
|
|
733
885
|
await this.adapter.setStateAsync(`${basePath}.consumption.dailyHT`, 0, true);
|
|
734
886
|
await this.adapter.setStateAsync(`${basePath}.consumption.dailyNT`, 0, true);
|
|
887
|
+
|
|
888
|
+
// Costs for HT/NT
|
|
889
|
+
const dailyCostHT = (await this.adapter.getStateAsync(`${basePath}.costs.dailyHT`))?.val || 0;
|
|
890
|
+
const dailyCostNT = (await this.adapter.getStateAsync(`${basePath}.costs.dailyNT`))?.val || 0;
|
|
891
|
+
await this.adapter.setStateAsync(`${basePath}.statistics.cost.lastDayHT`, dailyCostHT, true);
|
|
892
|
+
await this.adapter.setStateAsync(`${basePath}.statistics.cost.lastDayNT`, dailyCostNT, true);
|
|
893
|
+
await this.adapter.setStateAsync(`${basePath}.costs.dailyHT`, 0, true);
|
|
894
|
+
await this.adapter.setStateAsync(`${basePath}.costs.dailyNT`, 0, true);
|
|
735
895
|
}
|
|
736
896
|
|
|
737
897
|
await this.adapter.setStateAsync(`${basePath}.costs.daily`, 0, true);
|
|
738
898
|
|
|
739
|
-
// Update lastDayStart timestamp
|
|
740
|
-
|
|
899
|
+
// Update lastDayStart timestamp (use normalized timestamp if provided)
|
|
900
|
+
const timestamp = resetTimestamp || Date.now();
|
|
901
|
+
await this.adapter.setStateAsync(`${basePath}.statistics.timestamps.lastDayStart`, timestamp, true);
|
|
741
902
|
|
|
742
903
|
await this.adapter.setStateAsync(
|
|
743
|
-
`${basePath}.statistics.averageDaily`,
|
|
904
|
+
`${basePath}.statistics.consumption.averageDaily`,
|
|
744
905
|
calculator.roundToDecimals(dailyValue, 2),
|
|
745
906
|
true,
|
|
746
907
|
);
|
|
908
|
+
await this.adapter.setStateAsync(
|
|
909
|
+
`${basePath}.statistics.cost.averageDaily`,
|
|
910
|
+
calculator.roundToDecimals(dailyCostValue, 2),
|
|
911
|
+
true,
|
|
912
|
+
);
|
|
747
913
|
}
|
|
748
914
|
|
|
749
915
|
// Update totals if multiple meters exist
|
|
@@ -756,8 +922,9 @@ class BillingManager {
|
|
|
756
922
|
* Resets monthly counters
|
|
757
923
|
*
|
|
758
924
|
* @param {string} type - Utility type
|
|
925
|
+
* @param {number} [resetTimestamp] - Optional normalized timestamp (defaults to Date.now())
|
|
759
926
|
*/
|
|
760
|
-
async resetMonthlyCounters(type) {
|
|
927
|
+
async resetMonthlyCounters(type, resetTimestamp) {
|
|
761
928
|
this.adapter.log.info(`Resetting monthly counters for ${type}`);
|
|
762
929
|
|
|
763
930
|
// Get all meters for this type (main + additional meters)
|
|
@@ -780,13 +947,22 @@ class BillingManager {
|
|
|
780
947
|
const monthlyValue = monthlyState?.val || 0;
|
|
781
948
|
|
|
782
949
|
// Save last month consumption
|
|
783
|
-
await this.adapter.setStateAsync(`${basePath}.statistics.lastMonth`, monthlyValue, true);
|
|
950
|
+
await this.adapter.setStateAsync(`${basePath}.statistics.consumption.lastMonth`, monthlyValue, true);
|
|
951
|
+
|
|
952
|
+
// Save last month costs
|
|
953
|
+
const monthlyCostState = await this.adapter.getStateAsync(`${basePath}.costs.monthly`);
|
|
954
|
+
const monthlyCostValue = monthlyCostState?.val || 0;
|
|
955
|
+
await this.adapter.setStateAsync(`${basePath}.statistics.cost.lastMonth`, monthlyCostValue, true);
|
|
784
956
|
|
|
785
957
|
// For gas: also save volume
|
|
786
958
|
if (type === 'gas') {
|
|
787
959
|
const monthlyVolume = await this.adapter.getStateAsync(`${basePath}.consumption.monthlyVolume`);
|
|
788
960
|
const monthlyVolumeValue = monthlyVolume?.val || 0;
|
|
789
|
-
await this.adapter.setStateAsync(
|
|
961
|
+
await this.adapter.setStateAsync(
|
|
962
|
+
`${basePath}.statistics.consumption.lastMonthVolume`,
|
|
963
|
+
monthlyVolumeValue,
|
|
964
|
+
true,
|
|
965
|
+
);
|
|
790
966
|
await this.adapter.setStateAsync(`${basePath}.consumption.monthlyVolume`, 0, true);
|
|
791
967
|
}
|
|
792
968
|
|
|
@@ -797,20 +973,38 @@ class BillingManager {
|
|
|
797
973
|
const configType = getConfigType(type);
|
|
798
974
|
const htNtEnabled = this.adapter.config[`${configType}HtNtEnabled`] || false;
|
|
799
975
|
if (htNtEnabled) {
|
|
976
|
+
const monthlyHT = (await this.adapter.getStateAsync(`${basePath}.consumption.monthlyHT`))?.val || 0;
|
|
977
|
+
const monthlyNT = (await this.adapter.getStateAsync(`${basePath}.consumption.monthlyNT`))?.val || 0;
|
|
978
|
+
await this.adapter.setStateAsync(`${basePath}.statistics.consumption.lastMonthHT`, monthlyHT, true);
|
|
979
|
+
await this.adapter.setStateAsync(`${basePath}.statistics.consumption.lastMonthNT`, monthlyNT, true);
|
|
800
980
|
await this.adapter.setStateAsync(`${basePath}.consumption.monthlyHT`, 0, true);
|
|
801
981
|
await this.adapter.setStateAsync(`${basePath}.consumption.monthlyNT`, 0, true);
|
|
982
|
+
|
|
983
|
+
// Costs for HT/NT
|
|
984
|
+
const monthlyCostHT = (await this.adapter.getStateAsync(`${basePath}.costs.monthlyHT`))?.val || 0;
|
|
985
|
+
const monthlyCostNT = (await this.adapter.getStateAsync(`${basePath}.costs.monthlyNT`))?.val || 0;
|
|
986
|
+
await this.adapter.setStateAsync(`${basePath}.statistics.cost.lastMonthHT`, monthlyCostHT, true);
|
|
987
|
+
await this.adapter.setStateAsync(`${basePath}.statistics.cost.lastMonthNT`, monthlyCostNT, true);
|
|
988
|
+
await this.adapter.setStateAsync(`${basePath}.costs.monthlyHT`, 0, true);
|
|
989
|
+
await this.adapter.setStateAsync(`${basePath}.costs.monthlyNT`, 0, true);
|
|
802
990
|
}
|
|
803
991
|
|
|
804
992
|
await this.adapter.setStateAsync(`${basePath}.costs.monthly`, 0, true);
|
|
805
993
|
|
|
806
|
-
// Update lastMonthStart timestamp
|
|
807
|
-
|
|
994
|
+
// Update lastMonthStart timestamp (use normalized timestamp if provided)
|
|
995
|
+
const timestamp = resetTimestamp || Date.now();
|
|
996
|
+
await this.adapter.setStateAsync(`${basePath}.statistics.timestamps.lastMonthStart`, timestamp, true);
|
|
808
997
|
|
|
809
998
|
await this.adapter.setStateAsync(
|
|
810
|
-
`${basePath}.statistics.averageMonthly`,
|
|
999
|
+
`${basePath}.statistics.consumption.averageMonthly`,
|
|
811
1000
|
calculator.roundToDecimals(monthlyValue, 2),
|
|
812
1001
|
true,
|
|
813
1002
|
);
|
|
1003
|
+
await this.adapter.setStateAsync(
|
|
1004
|
+
`${basePath}.statistics.cost.averageMonthly`,
|
|
1005
|
+
calculator.roundToDecimals(monthlyCostValue, 2),
|
|
1006
|
+
true,
|
|
1007
|
+
);
|
|
814
1008
|
}
|
|
815
1009
|
|
|
816
1010
|
// Update totals if multiple meters exist
|
|
@@ -842,18 +1036,51 @@ class BillingManager {
|
|
|
842
1036
|
|
|
843
1037
|
this.adapter.log.debug(`Resetting yearly counter for ${basePath} (${label})`);
|
|
844
1038
|
|
|
1039
|
+
const yearlyState = await this.adapter.getStateAsync(`${basePath}.consumption.yearly`);
|
|
1040
|
+
const yearlyValue = yearlyState?.val || 0;
|
|
1041
|
+
await this.adapter.setStateAsync(`${basePath}.statistics.consumption.lastYear`, yearlyValue, true);
|
|
1042
|
+
|
|
1043
|
+
const yearlyCostState = await this.adapter.getStateAsync(`${basePath}.costs.yearly`);
|
|
1044
|
+
const yearlyCostValue = yearlyCostState?.val || 0;
|
|
1045
|
+
await this.adapter.setStateAsync(`${basePath}.statistics.cost.lastYear`, yearlyCostValue, true);
|
|
1046
|
+
|
|
845
1047
|
await this.adapter.setStateAsync(`${basePath}.consumption.yearly`, 0, true);
|
|
846
1048
|
|
|
847
1049
|
if (type === 'gas') {
|
|
1050
|
+
const yearlyVolume = await this.adapter.getStateAsync(`${basePath}.consumption.yearlyVolume`);
|
|
1051
|
+
const yearlyVolumeValue = yearlyVolume?.val || 0;
|
|
1052
|
+
await this.adapter.setStateAsync(
|
|
1053
|
+
`${basePath}.statistics.consumption.lastYearVolume`,
|
|
1054
|
+
yearlyVolumeValue,
|
|
1055
|
+
true,
|
|
1056
|
+
);
|
|
848
1057
|
await this.adapter.setStateAsync(`${basePath}.consumption.yearlyVolume`, 0, true);
|
|
849
1058
|
}
|
|
850
1059
|
|
|
1060
|
+
const configType = getConfigType(type);
|
|
1061
|
+
const htNtEnabled = this.adapter.config[`${configType}HtNtEnabled`] || false;
|
|
1062
|
+
if (htNtEnabled) {
|
|
1063
|
+
const yearlyHT = (await this.adapter.getStateAsync(`${basePath}.consumption.yearlyHT`))?.val || 0;
|
|
1064
|
+
const yearlyNT = (await this.adapter.getStateAsync(`${basePath}.consumption.yearlyNT`))?.val || 0;
|
|
1065
|
+
await this.adapter.setStateAsync(`${basePath}.statistics.consumption.lastYearHT`, yearlyHT, true);
|
|
1066
|
+
await this.adapter.setStateAsync(`${basePath}.statistics.consumption.lastYearNT`, yearlyNT, true);
|
|
1067
|
+
await this.adapter.setStateAsync(`${basePath}.consumption.yearlyHT`, 0, true);
|
|
1068
|
+
await this.adapter.setStateAsync(`${basePath}.consumption.yearlyNT`, 0, true);
|
|
1069
|
+
|
|
1070
|
+
const yearlyCostHT = (await this.adapter.getStateAsync(`${basePath}.costs.yearlyHT`))?.val || 0;
|
|
1071
|
+
const yearlyCostNT = (await this.adapter.getStateAsync(`${basePath}.costs.yearlyNT`))?.val || 0;
|
|
1072
|
+
await this.adapter.setStateAsync(`${basePath}.statistics.cost.lastYearHT`, yearlyCostHT, true);
|
|
1073
|
+
await this.adapter.setStateAsync(`${basePath}.statistics.cost.lastYearNT`, yearlyCostNT, true);
|
|
1074
|
+
await this.adapter.setStateAsync(`${basePath}.costs.yearlyHT`, 0, true);
|
|
1075
|
+
await this.adapter.setStateAsync(`${basePath}.costs.yearlyNT`, 0, true);
|
|
1076
|
+
}
|
|
1077
|
+
|
|
851
1078
|
await this.adapter.setStateAsync(`${basePath}.costs.yearly`, 0, true);
|
|
852
1079
|
await this.adapter.setStateAsync(`${basePath}.billing.notificationSent`, false, true);
|
|
853
1080
|
await this.adapter.setStateAsync(`${basePath}.billing.notificationChangeSent`, false, true);
|
|
854
1081
|
|
|
855
1082
|
// Update lastYearStart timestamp
|
|
856
|
-
await this.adapter.setStateAsync(`${basePath}.statistics.lastYearStart`, Date.now(), true);
|
|
1083
|
+
await this.adapter.setStateAsync(`${basePath}.statistics.timestamps.lastYearStart`, Date.now(), true);
|
|
857
1084
|
}
|
|
858
1085
|
|
|
859
1086
|
// Update totals if multiple meters exist
|
|
@@ -867,33 +1094,69 @@ class BillingManager {
|
|
|
867
1094
|
*
|
|
868
1095
|
* @param {string} type - Utility type
|
|
869
1096
|
* @param {object} meter - Meter object from multiMeterManager
|
|
1097
|
+
* @param {number} [resetTimestamp] - Optional normalized timestamp (defaults to Date.now())
|
|
870
1098
|
*/
|
|
871
|
-
async resetYearlyCountersForMeter(type, meter) {
|
|
1099
|
+
async resetYearlyCountersForMeter(type, meter, resetTimestamp) {
|
|
872
1100
|
const basePath = `${type}.${meter.name}`;
|
|
873
1101
|
const label = meter.displayName || meter.name;
|
|
874
1102
|
|
|
875
1103
|
this.adapter.log.debug(`Resetting yearly counter for ${basePath} (${label})`);
|
|
876
1104
|
|
|
1105
|
+
const yearlyState = await this.adapter.getStateAsync(`${basePath}.consumption.yearly`);
|
|
1106
|
+
const yearlyValue = yearlyState?.val || 0;
|
|
1107
|
+
await this.adapter.setStateAsync(`${basePath}.statistics.consumption.lastYear`, yearlyValue, true);
|
|
1108
|
+
|
|
1109
|
+
const yearlyCostState = await this.adapter.getStateAsync(`${basePath}.costs.yearly`);
|
|
1110
|
+
const yearlyCostValue = yearlyCostState?.val || 0;
|
|
1111
|
+
await this.adapter.setStateAsync(`${basePath}.statistics.cost.lastYear`, yearlyCostValue, true);
|
|
1112
|
+
|
|
877
1113
|
await this.adapter.setStateAsync(`${basePath}.consumption.yearly`, 0, true);
|
|
878
1114
|
|
|
879
1115
|
if (type === 'gas') {
|
|
1116
|
+
const yearlyVolume = await this.adapter.getStateAsync(`${basePath}.consumption.yearlyVolume`);
|
|
1117
|
+
const yearlyVolumeValue = yearlyVolume?.val || 0;
|
|
1118
|
+
await this.adapter.setStateAsync(
|
|
1119
|
+
`${basePath}.statistics.consumption.lastYearVolume`,
|
|
1120
|
+
yearlyVolumeValue,
|
|
1121
|
+
true,
|
|
1122
|
+
);
|
|
880
1123
|
await this.adapter.setStateAsync(`${basePath}.consumption.yearlyVolume`, 0, true);
|
|
881
1124
|
}
|
|
882
1125
|
|
|
1126
|
+
const configType = getConfigType(type);
|
|
1127
|
+
const htNtEnabled = this.adapter.config[`${configType}HtNtEnabled`] || false;
|
|
1128
|
+
if (htNtEnabled) {
|
|
1129
|
+
const yearlyHT = (await this.adapter.getStateAsync(`${basePath}.consumption.yearlyHT`))?.val || 0;
|
|
1130
|
+
const yearlyNT = (await this.adapter.getStateAsync(`${basePath}.consumption.yearlyNT`))?.val || 0;
|
|
1131
|
+
await this.adapter.setStateAsync(`${basePath}.statistics.consumption.lastYearHT`, yearlyHT, true);
|
|
1132
|
+
await this.adapter.setStateAsync(`${basePath}.statistics.consumption.lastYearNT`, yearlyNT, true);
|
|
1133
|
+
await this.adapter.setStateAsync(`${basePath}.consumption.yearlyHT`, 0, true);
|
|
1134
|
+
await this.adapter.setStateAsync(`${basePath}.consumption.yearlyNT`, 0, true);
|
|
1135
|
+
|
|
1136
|
+
const yearlyCostHT = (await this.adapter.getStateAsync(`${basePath}.costs.yearlyHT`))?.val || 0;
|
|
1137
|
+
const yearlyCostNT = (await this.adapter.getStateAsync(`${basePath}.costs.yearlyNT`))?.val || 0;
|
|
1138
|
+
await this.adapter.setStateAsync(`${basePath}.statistics.cost.lastYearHT`, yearlyCostHT, true);
|
|
1139
|
+
await this.adapter.setStateAsync(`${basePath}.statistics.cost.lastYearNT`, yearlyCostNT, true);
|
|
1140
|
+
await this.adapter.setStateAsync(`${basePath}.costs.yearlyHT`, 0, true);
|
|
1141
|
+
await this.adapter.setStateAsync(`${basePath}.costs.yearlyNT`, 0, true);
|
|
1142
|
+
}
|
|
1143
|
+
|
|
883
1144
|
await this.adapter.setStateAsync(`${basePath}.costs.yearly`, 0, true);
|
|
884
1145
|
await this.adapter.setStateAsync(`${basePath}.billing.notificationSent`, false, true);
|
|
885
1146
|
await this.adapter.setStateAsync(`${basePath}.billing.notificationChangeSent`, false, true);
|
|
886
1147
|
|
|
887
|
-
// Update lastYearStart timestamp
|
|
888
|
-
|
|
1148
|
+
// Update lastYearStart timestamp (use normalized timestamp if provided)
|
|
1149
|
+
const timestamp = resetTimestamp || Date.now();
|
|
1150
|
+
await this.adapter.setStateAsync(`${basePath}.statistics.timestamps.lastYearStart`, timestamp, true);
|
|
889
1151
|
}
|
|
890
1152
|
|
|
891
1153
|
/**
|
|
892
1154
|
* Resets weekly counters
|
|
893
1155
|
*
|
|
894
1156
|
* @param {string} type - Utility type
|
|
1157
|
+
* @param {number} [resetTimestamp] - Optional normalized timestamp (defaults to Date.now())
|
|
895
1158
|
*/
|
|
896
|
-
async resetWeeklyCounters(type) {
|
|
1159
|
+
async resetWeeklyCounters(type, resetTimestamp) {
|
|
897
1160
|
this.adapter.log.info(`Resetting weekly counters for ${type}`);
|
|
898
1161
|
|
|
899
1162
|
const meters = this.adapter.multiMeterManager?.getMetersForType(type) || [];
|
|
@@ -903,13 +1166,22 @@ class BillingManager {
|
|
|
903
1166
|
// Save last week consumption before reset
|
|
904
1167
|
const weeklyState = await this.adapter.getStateAsync(`${basePath}.consumption.weekly`);
|
|
905
1168
|
const weeklyValue = weeklyState?.val || 0;
|
|
906
|
-
await this.adapter.setStateAsync(`${basePath}.statistics.lastWeek`, weeklyValue, true);
|
|
1169
|
+
await this.adapter.setStateAsync(`${basePath}.statistics.consumption.lastWeek`, weeklyValue, true);
|
|
1170
|
+
|
|
1171
|
+
// Save last week costs before reset
|
|
1172
|
+
const weeklyCostState = await this.adapter.getStateAsync(`${basePath}.costs.weekly`);
|
|
1173
|
+
const weeklyCostValue = weeklyCostState?.val || 0;
|
|
1174
|
+
await this.adapter.setStateAsync(`${basePath}.statistics.cost.lastWeek`, weeklyCostValue, true);
|
|
907
1175
|
|
|
908
1176
|
// For gas: also save volume
|
|
909
1177
|
if (type === 'gas') {
|
|
910
1178
|
const weeklyVolume = await this.adapter.getStateAsync(`${basePath}.consumption.weeklyVolume`);
|
|
911
1179
|
const weeklyVolumeValue = weeklyVolume?.val || 0;
|
|
912
|
-
await this.adapter.setStateAsync(
|
|
1180
|
+
await this.adapter.setStateAsync(
|
|
1181
|
+
`${basePath}.statistics.consumption.lastWeekVolume`,
|
|
1182
|
+
weeklyVolumeValue,
|
|
1183
|
+
true,
|
|
1184
|
+
);
|
|
913
1185
|
await this.adapter.setStateAsync(`${basePath}.consumption.weeklyVolume`, 0, true);
|
|
914
1186
|
}
|
|
915
1187
|
|
|
@@ -920,13 +1192,25 @@ class BillingManager {
|
|
|
920
1192
|
const configType = getConfigType(type);
|
|
921
1193
|
const htNtEnabled = this.adapter.config[`${configType}HtNtEnabled`] || false;
|
|
922
1194
|
if (htNtEnabled) {
|
|
1195
|
+
const weeklyHT = (await this.adapter.getStateAsync(`${basePath}.consumption.weeklyHT`))?.val || 0;
|
|
1196
|
+
const weeklyNT = (await this.adapter.getStateAsync(`${basePath}.consumption.weeklyNT`))?.val || 0;
|
|
1197
|
+
await this.adapter.setStateAsync(`${basePath}.statistics.consumption.lastWeekHT`, weeklyHT, true);
|
|
1198
|
+
await this.adapter.setStateAsync(`${basePath}.statistics.consumption.lastWeekNT`, weeklyNT, true);
|
|
923
1199
|
await this.adapter.setStateAsync(`${basePath}.consumption.weeklyHT`, 0, true);
|
|
924
1200
|
await this.adapter.setStateAsync(`${basePath}.consumption.weeklyNT`, 0, true);
|
|
1201
|
+
|
|
1202
|
+
// Costs for HT/NT
|
|
1203
|
+
const weeklyCostHT = (await this.adapter.getStateAsync(`${basePath}.costs.weeklyHT`))?.val || 0;
|
|
1204
|
+
const weeklyCostNT = (await this.adapter.getStateAsync(`${basePath}.costs.weeklyNT`))?.val || 0;
|
|
1205
|
+
await this.adapter.setStateAsync(`${basePath}.statistics.cost.lastWeekHT`, weeklyCostHT, true);
|
|
1206
|
+
await this.adapter.setStateAsync(`${basePath}.statistics.cost.lastWeekNT`, weeklyCostNT, true);
|
|
925
1207
|
await this.adapter.setStateAsync(`${basePath}.costs.weeklyHT`, 0, true);
|
|
926
1208
|
await this.adapter.setStateAsync(`${basePath}.costs.weeklyNT`, 0, true);
|
|
927
1209
|
}
|
|
928
1210
|
|
|
929
|
-
|
|
1211
|
+
// Update lastWeekStart timestamp (use normalized timestamp if provided)
|
|
1212
|
+
const timestamp = resetTimestamp || Date.now();
|
|
1213
|
+
await this.adapter.setStateAsync(`${basePath}.statistics.timestamps.lastWeekStart`, timestamp, true);
|
|
930
1214
|
}
|
|
931
1215
|
|
|
932
1216
|
if (meters.length > 1) {
|