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/utils/helpers.js
CHANGED
|
@@ -166,6 +166,59 @@ async function safeSetObjectNotExists(adapter, id, obj) {
|
|
|
166
166
|
}
|
|
167
167
|
}
|
|
168
168
|
|
|
169
|
+
/**
|
|
170
|
+
* Safe execution wrapper for async operations with error handling.
|
|
171
|
+
* Catches errors and logs them without crashing the adapter.
|
|
172
|
+
*
|
|
173
|
+
* @param {object} adapter - Adapter instance
|
|
174
|
+
* @param {Function} fn - Async function to execute
|
|
175
|
+
* @param {string} context - Context description for error logging
|
|
176
|
+
* @param {any} fallback - Fallback value to return on error
|
|
177
|
+
* @returns {Promise<any>} Result of fn() or fallback on error
|
|
178
|
+
*/
|
|
179
|
+
async function safeExecute(adapter, fn, context, fallback = null) {
|
|
180
|
+
try {
|
|
181
|
+
return await fn();
|
|
182
|
+
} catch (error) {
|
|
183
|
+
if (adapter && adapter.log) {
|
|
184
|
+
adapter.log.error(`[${context}] ${error.message}`);
|
|
185
|
+
}
|
|
186
|
+
return fallback;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Debounce function to limit execution frequency
|
|
192
|
+
*
|
|
193
|
+
* @param {Function} fn - Function to debounce
|
|
194
|
+
* @param {number} delay - Delay in milliseconds
|
|
195
|
+
* @returns {Function} Debounced function
|
|
196
|
+
*/
|
|
197
|
+
function debounce(fn, delay) {
|
|
198
|
+
let timeoutId;
|
|
199
|
+
return function (...args) {
|
|
200
|
+
clearTimeout(timeoutId);
|
|
201
|
+
timeoutId = setTimeout(() => fn.apply(this, args), delay);
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Validates that a value is within a specified range
|
|
207
|
+
*
|
|
208
|
+
* @param {number} value - Value to validate
|
|
209
|
+
* @param {number} min - Minimum allowed value
|
|
210
|
+
* @param {number} max - Maximum allowed value
|
|
211
|
+
* @param {number} [defaultValue] - Default value if out of range
|
|
212
|
+
* @returns {number} Validated value or default
|
|
213
|
+
*/
|
|
214
|
+
function validateRange(value, min, max, defaultValue = min) {
|
|
215
|
+
const num = ensureNumber(value);
|
|
216
|
+
if (num < min || num > max) {
|
|
217
|
+
return defaultValue;
|
|
218
|
+
}
|
|
219
|
+
return num;
|
|
220
|
+
}
|
|
221
|
+
|
|
169
222
|
module.exports = {
|
|
170
223
|
ensureNumber,
|
|
171
224
|
roundToDecimals,
|
|
@@ -175,4 +228,7 @@ module.exports = {
|
|
|
175
228
|
getMonthsDifference,
|
|
176
229
|
normalizeMeterName,
|
|
177
230
|
safeSetObjectNotExists,
|
|
231
|
+
safeExecute,
|
|
232
|
+
debounce,
|
|
233
|
+
validateRange,
|
|
178
234
|
};
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* StateCache - Provides caching for adapter state operations.
|
|
5
|
+
* Reduces database queries by caching state values during update cycles.
|
|
6
|
+
*/
|
|
7
|
+
class StateCache {
|
|
8
|
+
/**
|
|
9
|
+
* Creates a new StateCache instance
|
|
10
|
+
*
|
|
11
|
+
* @param {object} adapter - ioBroker adapter instance
|
|
12
|
+
* @param {object} [options] - Configuration options
|
|
13
|
+
* @param {number} [options.maxAge] - Maximum cache age in milliseconds (default: 60s)
|
|
14
|
+
* @param {number} [options.maxSize] - Maximum number of cached entries
|
|
15
|
+
*/
|
|
16
|
+
constructor(adapter, options = {}) {
|
|
17
|
+
this.adapter = adapter;
|
|
18
|
+
this.cache = new Map();
|
|
19
|
+
this.maxAge = options.maxAge || 60000;
|
|
20
|
+
this.maxSize = options.maxSize || 1000;
|
|
21
|
+
this.hits = 0;
|
|
22
|
+
this.misses = 0;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Gets a state value, using cache if available
|
|
27
|
+
*
|
|
28
|
+
* @param {string} id - State ID
|
|
29
|
+
* @returns {Promise<any>} State value or null
|
|
30
|
+
*/
|
|
31
|
+
async get(id) {
|
|
32
|
+
const cached = this.cache.get(id);
|
|
33
|
+
const now = Date.now();
|
|
34
|
+
|
|
35
|
+
if (cached && now - cached.timestamp < this.maxAge) {
|
|
36
|
+
this.hits++;
|
|
37
|
+
return cached.value;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
this.misses++;
|
|
41
|
+
const state = await this.adapter.getStateAsync(id);
|
|
42
|
+
const value = state?.val ?? null;
|
|
43
|
+
|
|
44
|
+
this._set(id, value);
|
|
45
|
+
return value;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Gets a state object (with val, ack, ts), using cache if available
|
|
50
|
+
*
|
|
51
|
+
* @param {string} id - State ID
|
|
52
|
+
* @returns {Promise<object|null>} State object or null
|
|
53
|
+
*/
|
|
54
|
+
async getState(id) {
|
|
55
|
+
const cached = this.cache.get(`${id}_state`);
|
|
56
|
+
const now = Date.now();
|
|
57
|
+
|
|
58
|
+
if (cached && now - cached.timestamp < this.maxAge) {
|
|
59
|
+
this.hits++;
|
|
60
|
+
return cached.value;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
this.misses++;
|
|
64
|
+
const state = await this.adapter.getStateAsync(id);
|
|
65
|
+
|
|
66
|
+
this._set(`${id}_state`, state);
|
|
67
|
+
if (state) {
|
|
68
|
+
this._set(id, state.val);
|
|
69
|
+
}
|
|
70
|
+
return state;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Sets a state value and updates cache
|
|
75
|
+
*
|
|
76
|
+
* @param {string} id - State ID
|
|
77
|
+
* @param {any} value - Value to set
|
|
78
|
+
* @param {boolean} ack - Acknowledge flag
|
|
79
|
+
* @returns {Promise<void>}
|
|
80
|
+
*/
|
|
81
|
+
async set(id, value, ack = true) {
|
|
82
|
+
await this.adapter.setStateAsync(id, value, ack);
|
|
83
|
+
this._set(id, value);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Internal method to add to cache
|
|
88
|
+
*
|
|
89
|
+
* @param {string} id - State ID
|
|
90
|
+
* @param {any} value - Value to cache
|
|
91
|
+
*/
|
|
92
|
+
_set(id, value) {
|
|
93
|
+
// Evict oldest entries if cache is full
|
|
94
|
+
if (this.cache.size >= this.maxSize) {
|
|
95
|
+
const oldestKey = this.cache.keys().next().value;
|
|
96
|
+
this.cache.delete(oldestKey);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
this.cache.set(id, {
|
|
100
|
+
value,
|
|
101
|
+
timestamp: Date.now(),
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Clears the entire cache.
|
|
107
|
+
* Should be called at the end of each update cycle.
|
|
108
|
+
*/
|
|
109
|
+
clear() {
|
|
110
|
+
this.cache.clear();
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Invalidates a specific cache entry
|
|
115
|
+
*
|
|
116
|
+
* @param {string} id - State ID to invalidate
|
|
117
|
+
*/
|
|
118
|
+
invalidate(id) {
|
|
119
|
+
this.cache.delete(id);
|
|
120
|
+
this.cache.delete(`${id}_state`);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Gets cache statistics
|
|
125
|
+
*
|
|
126
|
+
* @returns {object} Cache statistics
|
|
127
|
+
*/
|
|
128
|
+
getStats() {
|
|
129
|
+
const total = this.hits + this.misses;
|
|
130
|
+
return {
|
|
131
|
+
hits: this.hits,
|
|
132
|
+
misses: this.misses,
|
|
133
|
+
hitRate: total > 0 ? `${((this.hits / total) * 100).toFixed(1)}%` : '0%',
|
|
134
|
+
size: this.cache.size,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Resets cache statistics
|
|
140
|
+
*/
|
|
141
|
+
resetStats() {
|
|
142
|
+
this.hits = 0;
|
|
143
|
+
this.misses = 0;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
module.exports = StateCache;
|
package/main.js
CHANGED
|
@@ -125,14 +125,10 @@ class UtilityMonitor extends utils.Adapter {
|
|
|
125
125
|
for (const meter of additionalMeters) {
|
|
126
126
|
if (meter && meter.name) {
|
|
127
127
|
if (!meter.contractStart) {
|
|
128
|
-
this.log.warn(
|
|
129
|
-
`${type.label} Zähler "${meter.name}": Kein Vertragsbeginn konfiguriert!`,
|
|
130
|
-
);
|
|
128
|
+
this.log.warn(`${type.label} Zähler "${meter.name}": Kein Vertragsbeginn konfiguriert!`);
|
|
131
129
|
}
|
|
132
130
|
if (!meter.sensorDP) {
|
|
133
|
-
this.log.warn(
|
|
134
|
-
`${type.label} Zähler "${meter.name}": Kein Sensor-Datenpunkt konfiguriert!`,
|
|
135
|
-
);
|
|
131
|
+
this.log.warn(`${type.label} Zähler "${meter.name}": Kein Sensor-Datenpunkt konfiguriert!`);
|
|
136
132
|
}
|
|
137
133
|
}
|
|
138
134
|
}
|