iobroker.smartloadmanager 0.1.0 → 0.1.2

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/io-package.json CHANGED
@@ -1,8 +1,34 @@
1
1
  {
2
2
  "common": {
3
3
  "name": "smartloadmanager",
4
- "version": "0.1.0",
4
+ "version": "0.1.2",
5
5
  "news": {
6
+ "0.1.2": {
7
+ "en": "Update dependencies",
8
+ "de": "Abhängigkeiten aktualisieren",
9
+ "ru": "Обновление зависимостей",
10
+ "pt": "Atualizar dependências",
11
+ "nl": "Afhankelijkheden bijwerken",
12
+ "fr": "Mise à jour des dépendances",
13
+ "it": "Dipendenze di aggiornamento",
14
+ "es": "Actualizar las dependencias",
15
+ "pl": "Aktualizuj zależności",
16
+ "uk": "Оновлення залежності",
17
+ "zh-cn": "更新依赖关系"
18
+ },
19
+ "0.1.1": {
20
+ "en": "Update dependencies",
21
+ "de": "Abhängigkeiten aktualisieren",
22
+ "ru": "Обновление зависимостей",
23
+ "pt": "Atualizar dependências",
24
+ "nl": "Afhankelijkheden bijwerken",
25
+ "fr": "Mise à jour des dépendances",
26
+ "it": "Dipendenze di aggiornamento",
27
+ "es": "Actualizar las dependencias",
28
+ "pl": "Aktualizuj zależności",
29
+ "uk": "Оновлення залежності",
30
+ "zh-cn": "更新依赖关系"
31
+ },
6
32
  "0.1.0": {
7
33
  "en": "Added a checkbox for activating consumers from the settings to the object tree",
8
34
  "de": "Hinzufügen eines Kontrollkästchens zur Aktivierung von Verbrauchern von den Einstellungen zum Objektbaum",
@@ -235,7 +261,15 @@
235
261
  "whatsappRecipients": "",
236
262
  "signalRecipients": "",
237
263
  "pushoverRecipients": "",
238
- "enableNotificationManager": false
264
+ "enableNotificationManager": false,
265
+ "enableForecast": false,
266
+ "forecastDefaultMinMinutes": 0,
267
+ "enableNotifications": false,
268
+ "notifyForecastBinary": false,
269
+ "notifyForecastPercent": false,
270
+ "notifyForecastBattery": false,
271
+ "forecastJsonDatapoint": "",
272
+ "notifyForecast": ""
239
273
  },
240
274
  "objects": [],
241
275
  "instanceObjects": []
package/main.js CHANGED
@@ -18,10 +18,21 @@ class ZeroFeedIn extends utils.Adapter {
18
18
  this.batteryTimer = null;
19
19
  this.checkRunning = false;
20
20
 
21
+ // 🔮 Forecast (PV forecast integration)
22
+ this.forecastEnabled = false;
23
+ this.forecastJsonDatapoint = null;
24
+ this.forecast = {
25
+ power: 0,
26
+ surplus: 0,
27
+ minutes: 0,
28
+ lastUpdate: 0,
29
+ };
30
+
21
31
  this.on('ready', this.onReady.bind(this));
22
32
  this.on('stateChange', this.onStateChange.bind(this));
23
33
  this.on('unload', this.onUnload.bind(this));
24
34
  }
35
+
25
36
  name2id(pName) {
26
37
  return (pName || '')
27
38
  .replace(this.FORBIDDEN_CHARS, '_') // ioBroker verbotene Zeichen
@@ -31,6 +42,7 @@ class ZeroFeedIn extends utils.Adapter {
31
42
  .replace(/_+/g, '_') // mehrere Unterstriche zusammenfassen
32
43
  .replace(/^_+|_+$/g, ''); // führende/trailing _ weg
33
44
  }
45
+
34
46
  // Wrapper für sichere Timer
35
47
  safeSetTimeout(fn, ms, ...args) {
36
48
  const safeMs = Math.min(Math.max(ms, 0), this.MAX_TIMEOUT);
@@ -48,6 +60,9 @@ class ZeroFeedIn extends utils.Adapter {
48
60
  return this.setInterval(fn, safeMs, ...args);
49
61
  }
50
62
 
63
+ // =====================================================================
64
+ // ============================ onReady ================================
65
+ // =====================================================================
51
66
  async onReady() {
52
67
  try {
53
68
  this.log.info(`Adapter started, PID: ${process.pid}`);
@@ -63,6 +78,10 @@ class ZeroFeedIn extends utils.Adapter {
63
78
  this.batteryControlModeDatapoint = this.config.batteryControlModeDatapoint || null;
64
79
  this.log.info(`Configured batteryControlModeDatapoint: ${this.batteryControlModeDatapoint}`);
65
80
 
81
+ // 🔮 Forecast config (PV forecast integration)
82
+ this.forecastEnabled = !!this.config.enableForecast;
83
+ this.forecastJsonDatapoint = this.config.forecastJsonDatapoint || null;
84
+
66
85
  this.consumerList = Array.isArray(this.config.consumer)
67
86
  ? this.config.consumer.filter(
68
87
  v =>
@@ -87,6 +106,12 @@ class ZeroFeedIn extends utils.Adapter {
87
106
 
88
107
  await this.checkAndCreateConsumerObjects();
89
108
  await this.createConsumerStates();
109
+
110
+ // 🔮 Forecast states (adapter-internal)
111
+ if (this.forecastEnabled) {
112
+ await this.createForecastStates();
113
+ }
114
+
90
115
  await this.initializeConsumerStatus();
91
116
  await this.subscribeStatesAsync('*');
92
117
  await this.subscribeForeignStatesAsync(this.feedInDatapoint);
@@ -94,6 +119,12 @@ class ZeroFeedIn extends utils.Adapter {
94
119
  await this.subscribeForeignStatesAsync(this.batteryControlModeDatapoint);
95
120
  }
96
121
 
122
+ // 🔮 Forecast subscribe + initial parse
123
+ if (this.forecastEnabled && this.forecastJsonDatapoint) {
124
+ await this.subscribeForeignStatesAsync(this.forecastJsonDatapoint);
125
+ await this.processPvforecastJson();
126
+ }
127
+
97
128
  await this.checkConsumers();
98
129
  } catch (error) {
99
130
  this.log.error(`Error in onReady: ${error.message}`);
@@ -102,6 +133,9 @@ class ZeroFeedIn extends utils.Adapter {
102
133
  }
103
134
  }
104
135
 
136
+ // =====================================================================
137
+ // ===================== Consumer object checks ========================
138
+ // =====================================================================
105
139
  async checkAndCreateConsumerObjects() {
106
140
  for (const v of this.consumerList) {
107
141
  if (v.datapoint) {
@@ -119,6 +153,9 @@ class ZeroFeedIn extends utils.Adapter {
119
153
  }
120
154
  }
121
155
 
156
+ // =====================================================================
157
+ // ========================= createConsumerStates ======================
158
+ // =====================================================================
122
159
  async createConsumerStates() {
123
160
  // Übergeordnetes Device erstellen - löst "intermediate objects missing" Problem
124
161
  await this.setObjectNotExistsAsync('consumer', {
@@ -250,6 +287,47 @@ class ZeroFeedIn extends utils.Adapter {
250
287
  native: {},
251
288
  });
252
289
 
290
+ // 🔮 Forecast options per consumer
291
+ await this.setObjectNotExistsAsync(`${channelId}.useForecast`, {
292
+ type: 'state',
293
+ common: {
294
+ name: 'Use forecast',
295
+ type: 'boolean',
296
+ role: 'switch',
297
+ read: true,
298
+ write: true,
299
+ def: v.useForecast || false,
300
+ },
301
+ native: {},
302
+ });
303
+
304
+ await this.setObjectNotExistsAsync(`${channelId}.forecastMinMinutes`, {
305
+ type: 'state',
306
+ common: {
307
+ name: 'Forecast min minutes',
308
+ type: 'number',
309
+ role: 'value.interval',
310
+ read: true,
311
+ write: true,
312
+ def: v.forecastMinMinutes || 0,
313
+ unit: 'min',
314
+ },
315
+ native: {},
316
+ });
317
+
318
+ await this.setObjectNotExistsAsync(`${channelId}.forecastStatus`, {
319
+ type: 'state',
320
+ common: {
321
+ name: 'Forecast status',
322
+ type: 'string',
323
+ role: 'text',
324
+ read: true,
325
+ write: false,
326
+ def: '',
327
+ },
328
+ native: {},
329
+ });
330
+
253
331
  if (v.ruletype === 'battery') {
254
332
  await this.setObjectNotExistsAsync(`${channelId}.batterySetpoint`, {
255
333
  type: 'state',
@@ -328,11 +406,226 @@ class ZeroFeedIn extends utils.Adapter {
328
406
 
329
407
  await this.setStateAsync(`${channelId}.active`, { val: v.enabled || false, ack: true });
330
408
 
409
+ // 🔮 Forecast states init per consumer
410
+ await this.setStateAsync(`${channelId}.useForecast`, { val: v.useForecast || false, ack: true });
411
+ await this.setStateAsync(`${channelId}.forecastMinMinutes`, {
412
+ val: v.forecastMinMinutes || 0,
413
+ ack: true,
414
+ });
415
+ await this.setStateAsync(`${channelId}.forecastStatus`, { val: '', ack: true });
416
+
331
417
  v.switchOnTime = finalOnTime;
332
418
  v.switchOffTime = finalOffTime;
333
419
  }
334
420
  }
335
421
 
422
+ // =====================================================================
423
+ // ========================= Forecast states ===========================
424
+ // =====================================================================
425
+ async createForecastStates() {
426
+ await this.setObjectNotExistsAsync('forecast', {
427
+ type: 'channel',
428
+ common: { name: 'Forecast' },
429
+ native: {},
430
+ });
431
+
432
+ await this.setObjectNotExistsAsync('forecast.power', {
433
+ type: 'state',
434
+ common: {
435
+ name: 'Forecast Power',
436
+ type: 'number',
437
+ role: 'value.power',
438
+ read: true,
439
+ write: false,
440
+ unit: 'W',
441
+ def: 0,
442
+ },
443
+ native: {},
444
+ });
445
+
446
+ await this.setObjectNotExistsAsync('forecast.surplus', {
447
+ type: 'state',
448
+ common: {
449
+ name: 'Forecast Surplus',
450
+ type: 'number',
451
+ role: 'value.power',
452
+ read: true,
453
+ write: false,
454
+ unit: 'W',
455
+ def: 0,
456
+ },
457
+ native: {},
458
+ });
459
+
460
+ await this.setObjectNotExistsAsync('forecast.minutes', {
461
+ type: 'state',
462
+ common: {
463
+ name: 'Forecast Minutes',
464
+ type: 'number',
465
+ role: 'value.interval',
466
+ read: true,
467
+ write: false,
468
+ unit: 'min',
469
+ def: 0,
470
+ },
471
+ native: {},
472
+ });
473
+
474
+ await this.setObjectNotExistsAsync('forecast.lastUpdate', {
475
+ type: 'state',
476
+ common: {
477
+ name: 'Forecast Last Update',
478
+ type: 'number',
479
+ role: 'value.time',
480
+ read: true,
481
+ write: false,
482
+ def: 0,
483
+ },
484
+ native: {},
485
+ });
486
+ }
487
+
488
+ // =====================================================================
489
+ // ========================= Forecast JSON parse =======================
490
+ // =====================================================================
491
+ async processPvforecastJson() {
492
+ try {
493
+ if (!this.forecastEnabled || !this.forecastJsonDatapoint) {
494
+ return;
495
+ }
496
+
497
+ const state = await this.getForeignStateAsync(this.forecastJsonDatapoint);
498
+ if (!state || !state.val) {
499
+ return;
500
+ }
501
+
502
+ let data;
503
+ try {
504
+ data = typeof state.val === 'string' ? JSON.parse(state.val) : state.val;
505
+ } catch {
506
+ this.log.warn('[Forecast] Invalid JSON in pvforecast datapoint');
507
+ return;
508
+ }
509
+
510
+ if (!Array.isArray(data) || data.length === 0) {
511
+ return;
512
+ }
513
+
514
+ const now = Date.now();
515
+ let best = null;
516
+
517
+ for (const entry of data) {
518
+ if (!entry || typeof entry.t !== 'number' || typeof entry.y !== 'number') {
519
+ continue;
520
+ }
521
+ if (entry.t < now) {
522
+ continue;
523
+ }
524
+ best = entry;
525
+ break;
526
+ }
527
+
528
+ if (!best) {
529
+ return;
530
+ }
531
+
532
+ // pvforecast liefert kW → wir rechnen auf W
533
+ const power = Math.round(best.y * 1000);
534
+ const minutes = Math.max(0, Math.round((best.t - now) / 60000));
535
+ const surplus = power - (this.config.baseload || 0);
536
+
537
+ this.forecast.power = power;
538
+ this.forecast.surplus = surplus > 0 ? surplus : 0;
539
+ this.forecast.minutes = minutes;
540
+ this.forecast.lastUpdate = Date.now();
541
+
542
+ await this.setStateAsync(`${this.namespace}.forecast.power`, { val: power, ack: true });
543
+ await this.setStateAsync(`${this.namespace}.forecast.surplus`, {
544
+ val: this.forecast.surplus,
545
+ ack: true,
546
+ });
547
+ await this.setStateAsync(`${this.namespace}.forecast.minutes`, { val: minutes, ack: true });
548
+ await this.setStateAsync(`${this.namespace}.forecast.lastUpdate`, {
549
+ val: this.forecast.lastUpdate,
550
+ ack: true,
551
+ });
552
+
553
+ this.log.debug(
554
+ `[Forecast] JSON parsed → power=${power}W, surplus=${this.forecast.surplus}W, minutes=${minutes}`,
555
+ );
556
+ } catch (error) {
557
+ this.log.error(`[Forecast] Error processing pvforecast JSON: ${error.message}`);
558
+ }
559
+ }
560
+
561
+ // =====================================================================
562
+ // ========================= Forecast helpers ==========================
563
+ // =====================================================================
564
+ getForecastPower(_v) {
565
+ return this.forecast?.power || 0;
566
+ }
567
+
568
+ getForecastMinutes(_v) {
569
+ return this.forecast?.minutes || 0;
570
+ }
571
+
572
+ isForecastAllowingSwitchOn(v) {
573
+ if (!this.forecastEnabled || !v.useForecast) {
574
+ return false;
575
+ }
576
+ const minMinutes = v.forecastMinMinutes || 0;
577
+ if (this.forecast.minutes > minMinutes) {
578
+ return false;
579
+ }
580
+ if (this.forecast.surplus >= v.performance) {
581
+ return true;
582
+ }
583
+ return false;
584
+ }
585
+
586
+ isForecastDelayingSwitchOff(v, _currentSurplus) {
587
+ if (!this.forecastEnabled || !v.useForecast) {
588
+ return false;
589
+ }
590
+ const minMinutes = v.forecastMinMinutes || 0;
591
+ if (this.forecast.minutes > minMinutes) {
592
+ return false;
593
+ }
594
+ if (this.forecast.surplus >= v.switchOnPoint) {
595
+ return true;
596
+ }
597
+ return false;
598
+ }
599
+
600
+ async setForecastState(v, idx, active, reason, power, minutes) {
601
+ try {
602
+ const safeName = this.name2id(v.name);
603
+ const channelId = `consumer.${idx}_${safeName}`;
604
+ const text = active
605
+ ? `${reason || 'forecast'} (${power || this.forecast.power || 0}W / ${minutes || this.forecast.minutes || 0}min)`
606
+ : '';
607
+ await this.setStateAsync(`${this.namespace}.${channelId}.forecastStatus`, {
608
+ val: text,
609
+ ack: true,
610
+ });
611
+ } catch (err) {
612
+ this.log.warn(`[Forecast] Failed to set forecastStatus: ${err.message}`);
613
+ }
614
+ }
615
+
616
+ async sendForecastNotification(type, message) {
617
+ try {
618
+ if (this.config.notifyForecast) {
619
+ await this.sendNotification('smartloadmanager', 'notify', message);
620
+ }
621
+ } catch (err) {
622
+ this.log.warn(`[Forecast] Failed to send forecast notification: ${err.message}`);
623
+ }
624
+ }
625
+
626
+ // =====================================================================
627
+ // ========================= Time window helper ========================
628
+ // =====================================================================
336
629
  timeWithinWindow(onTime, offTime, name) {
337
630
  if (!onTime || !offTime) {
338
631
  return true;
@@ -356,11 +649,23 @@ class ZeroFeedIn extends utils.Adapter {
356
649
  return within;
357
650
  }
358
651
 
652
+ // =====================================================================
653
+ // ========================= onStateChange =============================
654
+ // =====================================================================
359
655
  async onStateChange(id, state) {
360
656
  if (!state) {
361
657
  return;
362
658
  }
363
659
 
660
+ // 🔮 Forecast JSON datapoint changed
661
+ if (this.forecastEnabled && this.forecastJsonDatapoint && id === this.forecastJsonDatapoint) {
662
+ if (state.ack === true) {
663
+ await this.processPvforecastJson();
664
+ await this.checkConsumers();
665
+ }
666
+ return;
667
+ }
668
+
364
669
  // FeedIn Datapoint - fremder State, nur bei ack=true reagieren
365
670
  if (id === this.feedInDatapoint) {
366
671
  // Nur verarbeiten wenn der Wert vom anderen Adapter bestätigt wurde
@@ -450,6 +755,9 @@ class ZeroFeedIn extends utils.Adapter {
450
755
  }
451
756
  }
452
757
 
758
+ // =====================================================================
759
+ // ============================ checkConsumers =========================
760
+ // =====================================================================
453
761
  async checkConsumers() {
454
762
  if (this.checkRunning) {
455
763
  this.checkQueued = true;
@@ -590,6 +898,20 @@ class ZeroFeedIn extends utils.Adapter {
590
898
  await this.switchConsumerWithDelay(v, true, v.switchOnDelay || 0);
591
899
  feedIn -= v.performance;
592
900
  this.log.debug(`Remaining surplus after switch-on: ${feedIn - baseload} W`);
901
+ await this.setForecastState(v, idx, false, '', null, null);
902
+ } else if (!gridUsage && !isOn && this.isForecastAllowingSwitchOn(v)) {
903
+ const forecastPower = this.getForecastPower(v);
904
+ const minutes = this.getForecastMinutes(v);
905
+ this.log.debug(
906
+ `[Forecast] Early switch-on allowed for "${v.name}": ${forecastPower} W in ${minutes} min.`,
907
+ );
908
+ await this.setForecastState(v, idx, true, 'forecastSwitchOn', forecastPower, minutes);
909
+ await this.sendForecastNotification(
910
+ v.ruletype === 'percent' ? 'percent' : 'binary',
911
+ `🔮 Forecast: "${v.name}" will be switched on in ${minutes} minutes (${forecastPower} W expected).`,
912
+ );
913
+ await this.switchConsumerWithDelay(v, true, v.switchOnDelay || 0);
914
+ feedIn -= v.performance;
593
915
  } else if (gridUsage && !isOn) {
594
916
  this.log.debug(`Not switched on: grid usage active.`);
595
917
  } else if (!isOn) {
@@ -733,6 +1055,21 @@ class ZeroFeedIn extends utils.Adapter {
733
1055
  const shouldSwitchOff = !withinWindow || gridUsage || surplus < v.switchOffPoint;
734
1056
 
735
1057
  if (!v.alwaysOffAtTime && shouldSwitchOff) {
1058
+ // 🔮 Forecast delay switch-off (PV forecast integration)
1059
+ if (this.isForecastDelayingSwitchOff(v, surplus)) {
1060
+ const forecastPower = this.getForecastPower(v);
1061
+ const minutes = this.getForecastMinutes(v);
1062
+ this.log.debug(
1063
+ `[Forecast] Switch-off delayed for "${v.name}": ${forecastPower} W in ${minutes} min.`,
1064
+ );
1065
+ await this.setForecastState(v, idx, true, 'forecastDelayOff', forecastPower, minutes);
1066
+ await this.sendForecastNotification(
1067
+ v.ruletype === 'percent' ? 'percent' : 'binary',
1068
+ `🔮 Forecast: "${v.name}" remains active (${forecastPower} W expected in ${minutes} min).`,
1069
+ );
1070
+ continue;
1071
+ }
1072
+
736
1073
  if (v.timer && v.timerEnd && v.pendingAction === false) {
737
1074
  const remaining = Math.max(0, Math.round((v.timerEnd - Date.now()) / 1000));
738
1075
  this.log.debug(`Active timer for ${v.name}: ${remaining}s remaining`);
@@ -741,6 +1078,7 @@ class ZeroFeedIn extends utils.Adapter {
741
1078
  `Switching off: "${v.name}" - surplus ${surplus}W < switchOffPoint ${v.switchOffPoint}W, offDelay ${v.switchOffDelay || 0}s.`,
742
1079
  );
743
1080
  await this.switchConsumerWithDelay(v, false, v.switchOffDelay || 0);
1081
+ await this.setForecastState(v, idx, false, '', null, null);
744
1082
  }
745
1083
  }
746
1084
  }
@@ -778,6 +1116,9 @@ class ZeroFeedIn extends utils.Adapter {
778
1116
  }
779
1117
  }
780
1118
 
1119
+ // =====================================================================
1120
+ // ========================= controlPercentConsumer ====================
1121
+ // =====================================================================
781
1122
  async controlPercentConsumer(v) {
782
1123
  try {
783
1124
  const withinWindow = this.timeWithinWindow(v.switchOnTime, v.switchOffTime);
@@ -840,6 +1181,9 @@ class ZeroFeedIn extends utils.Adapter {
840
1181
  }
841
1182
  }
842
1183
 
1184
+ // =====================================================================
1185
+ // =============================== controlBattery ======================
1186
+ // =====================================================================
843
1187
  async controlBattery(v, feedIn) {
844
1188
  try {
845
1189
  this.log.debug(`[Battery] Checking for ${v.name}`);
@@ -996,6 +1340,9 @@ class ZeroFeedIn extends utils.Adapter {
996
1340
  }
997
1341
  }
998
1342
 
1343
+ // =====================================================================
1344
+ // ========================= switchConsumerWithDelay ===================
1345
+ // =====================================================================
999
1346
  async switchConsumerWithDelay(consumer, turnOn, delaySeconds) {
1000
1347
  const delay =
1001
1348
  delaySeconds !== undefined
@@ -1160,6 +1507,9 @@ class ZeroFeedIn extends utils.Adapter {
1160
1507
  }
1161
1508
  }
1162
1509
 
1510
+ // =====================================================================
1511
+ // ========================= sendNotification ==========================
1512
+ // =====================================================================
1163
1513
  // Zentrale, einheitliche Notification-Funktion
1164
1514
  // Aufruf: await this.sendNotification('smartloadmanager', 'info'|'notify'|'alert', 'Nachricht');
1165
1515
  async sendNotification(scope, category, message) {
@@ -1286,6 +1636,9 @@ class ZeroFeedIn extends utils.Adapter {
1286
1636
  }
1287
1637
  }
1288
1638
 
1639
+ // =====================================================================
1640
+ // ========================= initializeConsumerStatus ==================
1641
+ // =====================================================================
1289
1642
  async initializeConsumerStatus() {
1290
1643
  // Initiale Status-Updates
1291
1644
  for (const v of this.consumerList) {
@@ -1316,6 +1669,9 @@ class ZeroFeedIn extends utils.Adapter {
1316
1669
  return h * 60 + m;
1317
1670
  }
1318
1671
 
1672
+ // =====================================================================
1673
+ // =============================== onUnload ============================
1674
+ // =====================================================================
1319
1675
  async onUnload(callback) {
1320
1676
  try {
1321
1677
  if (this.percentTimer) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "iobroker.smartloadmanager",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "This adapter allows devices to be controlled via PV surplus and thus achieve zero feed-in.",
5
5
  "author": {
6
6
  "name": "quorle",
@@ -28,16 +28,16 @@
28
28
  "translate": "^3.0.1"
29
29
  },
30
30
  "devDependencies": {
31
- "@alcalzone/release-script": "^5.0.0",
32
- "@alcalzone/release-script-plugin-iobroker": "^4.0.0",
33
- "@alcalzone/release-script-plugin-license": "^4.0.0",
34
- "@alcalzone/release-script-plugin-manual-review": "^4.0.0",
31
+ "@alcalzone/release-script": "^5.1.1",
32
+ "@alcalzone/release-script-plugin-iobroker": "^5.1.2",
33
+ "@alcalzone/release-script-plugin-license": "^5.1.1",
34
+ "@alcalzone/release-script-plugin-manual-review": "^5.1.1",
35
35
  "@iobroker/adapter-dev": "^1.5.0",
36
36
  "@iobroker/eslint-config": "^2.2.0",
37
- "@iobroker/testing": "^5.1.1",
38
- "@tsconfig/node20": "^20.1.6",
39
- "@types/node": "^24.9.2",
40
- "typescript": "~5.9.3"
37
+ "@iobroker/testing": "^5.2.2",
38
+ "@tsconfig/node20": "^20.1.9",
39
+ "@types/node": "^25.6.0",
40
+ "typescript": "~6.0.2"
41
41
  },
42
42
  "main": "main.js",
43
43
  "files": [