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.
@@ -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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "iobroker.utility-monitor",
3
- "version": "1.5.0",
3
+ "version": "1.6.1",
4
4
  "description": "Monitor gas, water, and electricity consumption with cost calculation",
5
5
  "author": {
6
6
  "name": "fischi87",