iobroker.sun2000 1.0.1 → 1.2.2-alpha.0

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 CHANGED
@@ -30,7 +30,7 @@ Feel free to follow the discussions in the german [iobroker forum](https://forum
30
30
  ## Requirements
31
31
  * Node.js 20 or higher
32
32
  * ioBroker host (js-controller) 6.0.11 or higher
33
- * ioBroker admin 6.17.14 or higher
33
+ * ioBroker admin 7.4.10 or higher
34
34
 
35
35
  ## Documentation
36
36
 
@@ -64,6 +64,23 @@ browse in the [wiki](https://github.com/bolliy/ioBroker.sun2000/wiki)
64
64
  Placeholder for the next version (at the beginning of the line):
65
65
  ### **WORK IN PROGRESS**
66
66
  -->
67
+ ### 1.2.2-alpha.0 (2025-04-01)
68
+ * test release
69
+
70
+ ### 1.2.1 (2025-04-01)
71
+ * dependency update
72
+
73
+ ### 1.2.0 (2025-04-01)
74
+ * dependency and configuration updates
75
+ * fix: Object state sDongle.OSVersion to short
76
+ * new setting path for controlling the usableSurplusPower parameters `control.usableSurplus`
77
+
78
+ ### 1.1.0 (2025-03-19)
79
+ * startupTime/shutdownTime are read from the inverter as local time and not as UTC - fixed times are saved in path `derived`
80
+ * new state [usableSurplusPower](https://github.com/bolliy/ioBroker.sun2000/wiki/%C3%9Cberschuss-(surplus))
81
+ * control: checking and rounding integer numbers
82
+ * better solution for math rounding
83
+
67
84
  ### 1.0.1 (2025-03-01)
68
85
  * updated further case sensitivity of the object status name
69
86
  * require ioBroker admin 7.4.10 or higher #154
package/io-package.json CHANGED
@@ -1,8 +1,60 @@
1
1
  {
2
2
  "common": {
3
3
  "name": "sun2000",
4
- "version": "1.0.1",
4
+ "version": "1.2.2-alpha.0",
5
5
  "news": {
6
+ "1.2.2-alpha.0": {
7
+ "en": "test release",
8
+ "de": "prüfbericht",
9
+ "ru": "испытательный выпуск",
10
+ "pt": "liberação de teste",
11
+ "nl": "loslaten van de test",
12
+ "fr": "libération d'essai",
13
+ "it": "rilascio del test",
14
+ "es": "prueba de liberación",
15
+ "pl": "uwalnianie testowe",
16
+ "uk": "тестовий реліз",
17
+ "zh-cn": "测试发布"
18
+ },
19
+ "1.2.1": {
20
+ "en": "dependency update",
21
+ "de": "aktualisierung der abhängigkeit",
22
+ "ru": "обновление зависимостей",
23
+ "pt": "atualização de dependência",
24
+ "nl": "afhankelijkheidsupdate",
25
+ "fr": "mise à jour de la dépendance",
26
+ "it": "aggiornamento della dipendenza",
27
+ "es": "actualización de la dependencia",
28
+ "pl": "aktualizacja zależności",
29
+ "uk": "оновлення залежності",
30
+ "zh-cn": "依赖性更新"
31
+ },
32
+ "1.2.0": {
33
+ "en": "dependency and configuration updates updates\nfix: Object state sDongle.OSVersion to short\nnew setting path for controlling the usableSurplus parameters `control.usableSurplus`",
34
+ "de": "abhängigkeit und konfiguration updates\nfix: Objektzustand sDongle. OSVersion kurz\nneuer Einstellpfad zur Steuerung des Nutzbar Überschüssige Parameter `control.usableSurplus `",
35
+ "ru": "обновления зависимостей и конфигураций\nобсуждение Object State sDongle. OSVersion to Short\nновый путь настройки для контроля полезного Дополнительные параметры 'control.usable Surplus пункт",
36
+ "pt": "atualizações de dependência e configuração\ncorreção: Objeto estado sDongle. OSVersion to short\nnovo caminho de configuração para controlar a usável Parâmetros do Surplus `control.usableSurplus \"",
37
+ "nl": "afhankelijkheid en configuratie updates\nfix: Object state sDongle. OSVersion to short\nnieuw instellingspad voor het bedienen van het bruikbare Overschotparameters Controle.bruikbaarSurplus Wat",
38
+ "fr": "mises à jour de la dépendance et de la configuration\ncorrection : État de l'objet sDongle. OSVersion à court\nnouveau chemin de réglage pour contrôler l'utilisable Paramètres excédentaires \"control.usableExcédent \"",
39
+ "it": "aggiornamenti di dipendenza e configurazione\ncorrezione: SDongle di stato dell'oggetto. OSVersione a breve\nnuovo percorso di regolazione per il controllo dell'utilizzabile Parametri in eccesso `control.usableSurplus #",
40
+ "es": "actualizaciones de dependencia y configuración\nresolver: \"Objeto estado sDongle\". OSVersion to short\nnuevo camino de configuración para controlar el usable Parámetros adicionales `control.usableSurplus `",
41
+ "pl": "aktualizacje zależności i konfiguracji\nfix: Object state sDongle. OSVersion to short\nnowa ścieżka ustawień do sterowania użytecznym Parametry nadwyżki 'control.usableSurplus'",
42
+ "uk": "оновлення залежності та конфігурації\nвиправити: Об'єктний стан sDongle. OSVersion до короткого\nновий шлях налаштування для контролю Параметри Surplus `control.usableSurplus й",
43
+ "zh-cn": "依赖性和配置更新\n固定:对象状态 sDongle。 OSVsion 缩写\n用于控制可用的新设置路径 盈余参数 `控制.可使用余额' `"
44
+ },
45
+ "1.1.0": {
46
+ "en": "startupTime/shutdownTime are read from the inverter as local time and not as UTC - fixed times are saved in path `derived`\nnew state [usableSurplusPower](https://github.com/bolliy/ioBroker.sun2000/wiki/%C3%9Cberschuss-(surplus))\ncontrol: checking and rounding integer numbers\nbetter solution for math rounding",
47
+ "de": "startzeit/Shutdown Zeit wird vom Wechselrichter als Ortszeit gelesen und nicht als UTC - Festzeiten werden im Pfad gespeichert `derived `\nneuer Staat [usableSurplusPower](https://github.com/bolliy/ioBroker.sun2000/wiki/%C3%9Cberschuss-(surplus))\nsteuerung: überprüfung und rundung ganzzahliger zahlen\nbessere lösung für matherundung",
48
+ "ru": "startupTime/Sutdown Время считывается с инвертора как локальное время, а не как UTC - фиксированное время сохраняется в пути пункт\nновое состояние [usableSurplusPower] (https://github.com/bolliy/ioBroker.sun2000/wiki/%C3%9Cberschuss-(surplus))\nконтроль: проверка и округление целых чисел\nлучшее решение для математического округления",
49
+ "pt": "startupTime/shutdown O tempo é lido do inversor como hora local e não como UTC - os tempos fixos são salvos no caminho `derivod \"\nnovo estado [usableSurplusPower](https://github.com/bolliy/ioBroker.sun2000/wiki/%C3%9Cberschuss-(surplus)))\ncontrole: números inteiros de verificação e arredondamento\nmelhor solução para arredondamento de matemática",
50
+ "nl": "opstartenTijd/afsluiten De tijd wordt van de inverter gelezen als lokale tijd en niet als UTC - vaste tijden worden opgeslagen in het pad uit Wat\nnieuwe staat [bruikbaarSurplusPower](https://github.com/bolliy/ioBroker.sun2000/wiki/%C3%9Cberschuss-(surplus))\ncontrole: controle en afronding van gehele getallen\nbetere oplossing voor wiskunde afronden",
51
+ "fr": "startupTime/shutdown Le temps est lu à partir de l'onduleur en tant qu'heure locale et non pas en tant que UTC - les temps fixes sont sauvegardés dans le chemin « dérivé \"\nnouvel état [utilisableSurplusPower](https://github.com/bolliy/ioBroker.sun2000/wiki/%C3%9Cberschuss-(surplus))\ncontrôle: contrôle et arrondi des nombres entiers\nmeilleure solution pour arrondir les maths",
52
+ "it": "startupTime/shutdown Il tempo viene letto dall'inverter come ora locale e non come UTC - i tempi fissi vengono salvati nel percorso `derivati #\nnuovo stato [usableSurplusPower](https://github.com/bolliy/ioBroker.sun2000/wiki/%C3%9Cberschuss-(plus)\ncontrollo: controllo e arrotondamento numeri interi\nmigliore soluzione per arrotondamento di matematica",
53
+ "es": "startupTime/shutdown El tiempo se lee desde el inversor como hora local y no como UTC - los tiempos fijos se salvan en el camino `derived `\nnuevo estado [utilizableSurplusPower](https://github.com/bolliy/ioBroker.sun2000/wiki/%C3%9Cberschuss-(superávit))\ncontrol: números enteros de comprobación y redondeo\nmejor solución para redondeo de matemáticas",
54
+ "pl": "startupTime / shutdown Czas jest odczytywany z inwertera jako czasu lokalnego, a nie jako UTC - stałe czasy są zapisywane w ścieżce 'pochodnej'\nnową ustawę [usableSurplusPower] (https: / / github.com / bolliy / ioBroker.sun2000 / wiki /% C3% 9Cberschuss- (nadwyżka))\nkontrola: sprawdzanie i zaokrąglanie liczb całkowitych\nlepsze rozwiązanie do zaokrąglania matematycznego",
55
+ "uk": "javaScript licenses API Веб-сайт Час читає з інвертора як локальний час, так і не як UTC - виправлені часи зберігаються в шляху `derived й\nновий стан [usableSurplusPower](https://github.com/bolliy/ioBroker.sun2000/wiki/%C3%9Cberschuss-(surplus)))\nконтроль: перевірка та округлення цілих чисел\nкраще рішення для закруглення математики",
56
+ "zh-cn": "启动时间/下调 时间从反转器读作本地时间,而不是世界协调时 - 固定时间保存在路径中`衍生' `\n新状态[可使用SurpusPower](https://github.com/bolliy/ioBroker.sun2000/wiki/%C3%9 Cberschuss-(盈余))\n控件: 检查和四舍五入整数\n更好的数学四舍五入解决方案"
57
+ },
6
58
  "1.0.1": {
7
59
  "en": "updated further case sensitivity of the object status name\nrequire ioBroker admin 7.4.10 or higher #154",
8
60
  "de": "aktualisierte weitere fallempfindlichkeit des objektstatusnamens\nioBroker admin 7.4.10 oder höher #154",
@@ -41,58 +93,6 @@
41
93
  "pl": "aktualizacje zależności i konfiguracji\nsterowanie: jeśli akumulator nie jest uruchomiony, zdarzenia związane z akumulatorem są odrzucane\nmodbus- proxy: dostosowane zaawansowane logowanie",
42
94
  "uk": "оновлення залежності та конфігурації\nконтроль: якщо акумулятор не працює, події, пов'язані з акумулятором, відкидаються\nmodbus-proxy: регульований розширений журналювання",
43
95
  "zh-cn": "依赖和配置更新\n控件: 如果电池没有运行, 与电池相关的事件会被丢弃\nmodbus-代用:经过调整的高级记录"
44
- },
45
- "0.18.0": {
46
- "en": "dependency and configuration updates\nmodbus-proxy: the modbus ID 250 is mapped to ID 0",
47
- "de": "abhängigkeits- und konfigurationsupdates\nmodbus-proxy: die modbus ID 250 wird auf ID 0 abgebildet",
48
- "ru": "обновления зависимости и конфигурации\nmodbus-proxy: modbus ID 250 отображается на ID 0",
49
- "pt": "atualizações de dependência e configuração\nmodbus-proxy: o modbus ID 250 é mapeado para ID 0",
50
- "nl": "afhankelijkheid en configuratie-updates\nmodbus-proxy: de modbus ID 250 is toegewezen aan ID 0",
51
- "fr": "mises à jour de la dépendance et de la configuration\nmodbus-proxy: le modbus ID 250 est mapé à ID 0",
52
- "it": "aggiornamenti di dipendenza e configurazione\nmodbus-proxy: il modbus ID 250 è mappato su ID 0",
53
- "es": "actualizaciones de dependencia y configuración\nmodbus-proxy: el modbus ID 250 se asigna al ID 0",
54
- "pl": "aktualizacje zależności i konfiguracji\nmodbus- proxy: modbus ID 250 jest odwzorowany na ID 0",
55
- "uk": "оновлення залежності та конфігурації\nmodbus-proxy: modbus ID 250 is mapped to ID 0",
56
- "zh-cn": "依赖和配置更新\nmodbus-代用:modbus ID 250被映射到ID 0"
57
- },
58
- "0.17.1": {
59
- "en": "during the device status \"Standby: detecting irradiation\" (0x0002) register data is now also read from the inverter\nThe reading order of the battery data has been adjusted",
60
- "de": "während des Gerätestatus \"Standby: Detektion von Bestrahlung\" (0x0002) werden nun auch Registerdaten aus dem Wechselrichter gelesen\nDie Lesereihenfolge der Batteriedaten wurde angepasst",
61
- "ru": "во время состояния устройства «Стандби: обнаружение облучения» (0x0002) данные регистров теперь также читаются из инвертора\nПорядок чтения данных батареи был скорректирован",
62
- "pt": "durante o status do dispositivo \"Standby: detecção de irradiação\" (0x0002) dados de registro agora também é lido a partir do inversor\nA ordem de leitura dos dados da bateria foi ajustada",
63
- "nl": "tijdens de apparaatstatus \"Standby: detectie van bestraling\" (0x0002) worden nu ook gegevens van de omvormer gelezen\nDe leesvolgorde van de batterijgegevens is aangepast",
64
- "fr": "pendant l'état de l'appareil \"Standby: détecter l'irradiation\" (0x0002) les données du registre sont maintenant également lues à partir de l'onduleur\nL'ordre de lecture des données de la batterie a été ajusté",
65
- "it": "durante lo stato del dispositivo \"Standby: rilevamento dell'irradiazione\" (0x0002) i dati di registro sono ora letti anche dall'inverter\nL'ordine di lettura dei dati della batteria è stato regolato",
66
- "es": "durante el estado del dispositivo \"Standby: detecting irradiation\" (0x0002) datos del registro ahora también se lee desde el inversor\nEl orden de lectura de los datos de la batería se ha ajustado",
67
- "pl": "podczas statusu urządzenia \"Standby: detection irradiation\" (0x0002) dane rejestru są teraz również odczytywane z inwertera\nKolejność odczytu danych baterii została dostosowana",
68
- "uk": "при статусі пристрою \"Стандарт: виявлення опромінення\" (0x0002) реєстраційні дані тепер також читайте з інвертора\nВиправлено порядок читання даних акумулятора",
69
- "zh-cn": "在设备状态“ Standby: 检测辐照” (0x0002) 注册数据现在也从反转器读取\n电池数据的读取顺序已调整"
70
- },
71
- "0.17.0": {
72
- "en": "adjust for Responsive Design #134\nmigrate to ESLint 9.x\nnode >= v18.18.0\nmodbus-proxy: enabled reading data via input register",
73
- "de": "anpassung für Responsive Design #134\nmigration auf ESLint 9.x\nknoten >= v18.18.0\nmodbus-proxy: lesedaten über eingaberegister aktiviert",
74
- "ru": "#134\nмигрировать в ESLint 9.x\nnode >= v18.18.0\nmodbus-proxy: включено чтение данных через регистр ввода",
75
- "pt": "ajustar para Design responsivo #134\nmigrar para ESLint 9.x\nnode >= v18.18.0\nmodbus-proxy: dados de leitura habilitados via registro de entrada",
76
- "nl": "aanpassen voor Responsive Design #134\nmigreren naar ESLint 9.x\nn.v.t\nmodbus-proxy: leesgegevens ingeschakeld via invoerregister",
77
- "fr": "ajuster pour Responsive Design #134\nmigrer vers ESLint 9.x\nnoeud >= v18.18.0\nmodbus-proxy: activé la lecture des données via le registre d'entrée",
78
- "it": "regolazione per Responsive Design #134\nmigrare a ESLint 9.x\nnodo >= v18.18.0\nmodbus-proxy: dati di lettura abilitati tramite registro input",
79
- "es": "ajuste para el diseño responsable #134\nmigrar a ESLint 9.x\nnodo\nmodbus-proxy: datos de lectura habilitados mediante registro de entrada",
80
- "pl": "dostosowanie dla Responsive Design # 134\nmigrate do ESLint 9.x\nwęzeł > = v18.18.0\nmodbus- proxy: włączone odczytywanie danych poprzez rejestr wejściowy",
81
- "uk": "конфігурація для відповідального дизайну #134\nmigrate до ESLint 9.x\nвершина >= v18.18.0\nmodbus-proxy: ввімкнено дані читання через вхідний реєстр",
82
- "zh-cn": "调整响应设计 # 134\n迁移到 ESLint 9.x\n节点 {v18.18.0}\nmodbus- 代理: 启用通过输入寄存器读取数据"
83
- },
84
- "0.16.0": {
85
- "en": "dependency and configuration updates\nread additional register data of Huawei Emma",
86
- "de": "abhängigkeits- und konfigurationsupdates\nweitere Registrierungsdaten von Huawei Emma lesen",
87
- "ru": "обновления зависимости и конфигурации\nпрочитать дополнительные данные реестра Huawei Эмма",
88
- "pt": "atualizações de dependência e configuração\nleia dados de registro adicionais de Huawei Emma",
89
- "nl": "afhankelijkheid en configuratie-updates\nlees extra registergegevens van Huawei Emma",
90
- "fr": "mises à jour de la dépendance et de la configuration\nlire les données supplémentaires du registre de Huawei Emma",
91
- "it": "aggiornamenti di dipendenza e configurazione\nleggere ulteriori dati di registro di Huawei Emma",
92
- "es": "actualizaciones de dependencia y configuración\nleer más datos de registro de Huawei Emma",
93
- "pl": "aktualizacje zależności i konfiguracji\nprzeczytaj dodatkowe dane rejestru Huawei Emma",
94
- "uk": "оновлення залежності та конфігурації\nчитати додаткові реєстраційні дані Huawei Emma",
95
- "zh-cn": "依赖和配置更新\n读取 Huawei Emma 的额外注册数据"
96
96
  }
97
97
  },
98
98
  "titleLang": {
@@ -0,0 +1,187 @@
1
+ 'use strict';
2
+ //const { deviceType } = require(`${__dirname}/../types.js`);
3
+ class ConfigMap {
4
+ constructor(adapterInstance) {
5
+ this.adapter = adapterInstance;
6
+ this.log = this.adapter.logger;
7
+ this._serviceMap = new Map();
8
+ this._eventMap = new Map();
9
+ this._initialized = false;
10
+ this._path = 'control';
11
+
12
+ this.serviceFields = [
13
+ {
14
+ state: {
15
+ id: 'usableSurplus.minSoc',
16
+ name: 'minmum SoC',
17
+ type: 'number',
18
+ unit: '%',
19
+ role: 'level',
20
+ desc: 'Use of battery charging power above the specified SoC value (%)',
21
+ },
22
+ fn: async event => {
23
+ if (event.value > 100) {
24
+ event.value = 100;
25
+ }
26
+ if (event.value < 0) {
27
+ event.value = 0;
28
+ }
29
+ return true;
30
+ },
31
+ },
32
+ {
33
+ state: {
34
+ id: 'usableSurplus.bufferSoc',
35
+ name: 'buffer SoC',
36
+ type: 'number',
37
+ unit: '%',
38
+ role: 'level',
39
+ desc: 'battery is used as a buffer above Soc (%) value',
40
+ },
41
+ fn: async event => {
42
+ if (event.value > 100) {
43
+ event.value = 100;
44
+ }
45
+ if (event.value < 0) {
46
+ event.value = 0;
47
+ }
48
+ return true;
49
+ },
50
+ },
51
+ {
52
+ state: {
53
+ id: 'usableSurplus.bufferPower',
54
+ name: 'Discharge power from buffer',
55
+ type: 'number',
56
+ unit: 'W',
57
+ role: 'level.power',
58
+ desc: 'battery is used as a buffer with power',
59
+ },
60
+ fn: async event => {
61
+ if (event.value > 5000) {
62
+ event.value = 5000;
63
+ }
64
+ if (event.value < 0) {
65
+ event.value = 0;
66
+ }
67
+ return true;
68
+ },
69
+ },
70
+ {
71
+ state: {
72
+ id: 'usableSurplus.residualPower',
73
+ name: 'residual Power',
74
+ type: 'number',
75
+ unit: 'W',
76
+ role: 'level.power',
77
+ desc: 'Sets the target operating point of the surplus regulation',
78
+ },
79
+ fn: async event => {
80
+ if (event.value > 1000) {
81
+ event.value = 1000;
82
+ }
83
+ if (event.value < -1000) {
84
+ event.value = -1000;
85
+ }
86
+ return true;
87
+ },
88
+ },
89
+ ];
90
+ }
91
+
92
+ async init() {
93
+ for (const item of this.serviceFields) {
94
+ if (item?.state) {
95
+ this._serviceMap.set(item.state.id, item);
96
+ }
97
+ }
98
+ //read value
99
+ for (const entry of this._serviceMap.values()) {
100
+ await this._initState(`${this._path}.`, entry.state);
101
+ const state = await this.adapter.getState(`${this._path}.${entry.state.id}`);
102
+
103
+ if (state) {
104
+ this.set(entry.state.id, state);
105
+ }
106
+ }
107
+ this.adapter.subscribeStates(`${this._path}.*`);
108
+ this._initialized = true;
109
+ /*
110
+ if (this._initialized) {
111
+ this.log.info('Control: Config map initialized');
112
+ }
113
+ */
114
+ }
115
+
116
+ /**
117
+ * @description Check if the value of the event is a number and optionally round it to the nearest integer.
118
+ * @param {object} event The event object
119
+ * @param {boolean} [round] If true, the value is rounded to the nearest integer
120
+ * @returns {boolean} True if the value is a number, false otherwise
121
+ */
122
+ isNumber(event, round = true) {
123
+ if (isNaN(event.value)) {
124
+ return false;
125
+ }
126
+ if (round) event.value = Math.round(event.value);
127
+ return true;
128
+ }
129
+
130
+ get(id) {
131
+ return this._eventMap.get(id);
132
+ }
133
+
134
+ set(id, state) {
135
+ const service = this._serviceMap.get(id);
136
+ if (state && service) {
137
+ if (state.val !== null) {
138
+ if (!state.ack) this.log.info(`Control: Event - state: ${this._path}.${id} changed: ${state.val} ack: ${state.ack}`);
139
+ const event = this._eventMap.get(id);
140
+ if (event) {
141
+ event.value = state.val;
142
+ event.ack = state.ack;
143
+ } else {
144
+ this._eventMap.set(id, { id: id, value: state.val, ack: state.ack });
145
+ }
146
+ this._process(id);
147
+ }
148
+ }
149
+ }
150
+
151
+ //state
152
+ async _initState(path, state) {
153
+ await this.adapter.extendObject(path + state.id, {
154
+ type: 'state',
155
+ common: {
156
+ name: state.name,
157
+ type: state.type,
158
+ role: state.role,
159
+ unit: state.unit,
160
+ desc: state.desc,
161
+ read: true,
162
+ write: true,
163
+ },
164
+ native: {},
165
+ });
166
+ }
167
+
168
+ async _process(id) {
169
+ const event = this.get(id);
170
+ const service = this._serviceMap.get(event.id);
171
+ if (event.value !== null && service.fn) {
172
+ if (service.state.type === 'number') {
173
+ if (!this.isNumber(event)) {
174
+ this.log.warn(`Control: Event is discarded because the value ${event.value} is not a number. State: ${this._path}.${event.id}`);
175
+ return;
176
+ }
177
+ event.value = Math.round(event.value * 100) / 100;
178
+ }
179
+ if (await service.fn(event)) {
180
+ await this.adapter.setState(`${this._path}.${event.id}`, { val: event.value, ack: true });
181
+ event.ack = true;
182
+ }
183
+ }
184
+ }
185
+ }
186
+
187
+ module.exports = ConfigMap;
@@ -42,7 +42,7 @@ class ServiceQueueMap {
42
42
  }
43
43
  const ret = await this._writeRegisters(47075, dataType.numToArray(event.value, dataType.uint32));
44
44
  if (ret) {
45
- this.inverterInfo.instance.stateCache.set(`${this.inverterInfo.path}.${event.id}`, event.value);
45
+ this.inverterInfo.instance.stateCache.set(`${this.inverterInfo.path}.${event.id}`, event.value, { type: 'number' });
46
46
  }
47
47
  return ret;
48
48
  },
@@ -67,7 +67,7 @@ class ServiceQueueMap {
67
67
  }
68
68
  const ret = await this._writeRegisters(47077, dataType.numToArray(event.value, dataType.uint32));
69
69
  if (ret) {
70
- this.inverterInfo.instance.stateCache.set(`${this.inverterInfo.path}.${event.id}`, event.value);
70
+ this.inverterInfo.instance.stateCache.set(`${this.inverterInfo.path}.${event.id}`, event.value, { type: 'number' });
71
71
  }
72
72
  return ret;
73
73
  },
@@ -137,7 +137,7 @@ class ServiceQueueMap {
137
137
  }
138
138
  const ret = await this._writeRegisters(47081, dataType.numToArray(event.value * 10, dataType.uint16));
139
139
  if (ret) {
140
- this.inverterInfo.instance.stateCache.set(`${this.inverterInfo.path}.${event.id}`, event.value);
140
+ this.inverterInfo.instance.stateCache.set(`${this.inverterInfo.path}.${event.id}`, event.value, { type: 'number' });
141
141
  }
142
142
  return ret;
143
143
  },
@@ -161,7 +161,7 @@ class ServiceQueueMap {
161
161
  }
162
162
  const ret = await this._writeRegisters(47082, dataType.numToArray(event.value * 10, dataType.uint16));
163
163
  if (ret) {
164
- this.inverterInfo.instance.stateCache.set(`${this.inverterInfo.path}.${event.id}`, event.value);
164
+ this.inverterInfo.instance.stateCache.set(`${this.inverterInfo.path}.${event.id}`, event.value, { type: 'number' });
165
165
  }
166
166
  return ret;
167
167
  },
@@ -185,7 +185,7 @@ class ServiceQueueMap {
185
185
  }
186
186
  const ret = await this._writeRegisters(47088, dataType.numToArray(event.value * 10, dataType.uint16));
187
187
  if (ret) {
188
- this.inverterInfo.instance.stateCache.set(`${this.inverterInfo.path}.${event.id}`, event.value);
188
+ this.inverterInfo.instance.stateCache.set(`${this.inverterInfo.path}.${event.id}`, event.value, { type: 'number' });
189
189
  }
190
190
  return ret;
191
191
  },
@@ -202,7 +202,7 @@ class ServiceQueueMap {
202
202
  }
203
203
  const ret = await this._writeRegisters(47086, dataType.numToArray(event.value, dataType.uint16));
204
204
  if (ret) {
205
- this.inverterInfo.instance.stateCache.set(`${this.inverterInfo.path}.${event.id}`, event.value);
205
+ this.inverterInfo.instance.stateCache.set(`${this.inverterInfo.path}.${event.id}`, event.value, { type: 'number' });
206
206
  }
207
207
  return ret;
208
208
  },
@@ -306,7 +306,7 @@ class ServiceQueueMap {
306
306
  }
307
307
  const ret = await this._writeRegisters(47101, dataType.numToArray(event.value * 10, dataType.uint16));
308
308
  if (ret) {
309
- this.inverterInfo.instance.stateCache.set(`${this.inverterInfo.path}.${event.id}`, event.value);
309
+ this.inverterInfo.instance.stateCache.set(`${this.inverterInfo.path}.${event.id}`, event.value, { type: 'number' });
310
310
  }
311
311
  return ret;
312
312
  },
@@ -395,7 +395,7 @@ class ServiceQueueMap {
395
395
  }
396
396
  const ret = await this._writeRegisters(47102, dataType.numToArray(event.value * 10, dataType.uint16));
397
397
  if (ret) {
398
- this.inverterInfo.instance.stateCache.set(`${this.inverterInfo.path}.${event.id}`, event.value);
398
+ this.inverterInfo.instance.stateCache.set(`${this.inverterInfo.path}.${event.id}`, event.value, { type: 'number' });
399
399
  }
400
400
  return ret;
401
401
  },
@@ -420,7 +420,7 @@ class ServiceQueueMap {
420
420
  }
421
421
  const ret = await this._writeRegisters(47416, dataType.numToArray(event.value * 1000, dataType.uint32));
422
422
  if (ret) {
423
- this.inverterInfo.instance.stateCache.set(`${this.inverterInfo.path}.${event.id}`, event.value);
423
+ this.inverterInfo.instance.stateCache.set(`${this.inverterInfo.path}.${event.id}`, event.value, { type: 'number' });
424
424
  }
425
425
  return ret;
426
426
  },
@@ -444,7 +444,7 @@ class ServiceQueueMap {
444
444
  }
445
445
  const ret = await this._writeRegisters(47418, dataType.numToArray(event.value * 10, dataType.int16));
446
446
  if (ret) {
447
- this.inverterInfo.instance.stateCache.set(`${this.inverterInfo.path}.${event.id}`, event.value);
447
+ this.inverterInfo.instance.stateCache.set(`${this.inverterInfo.path}.${event.id}`, event.value, { type: 'number' });
448
448
  }
449
449
  return ret;
450
450
  },
@@ -468,7 +468,7 @@ class ServiceQueueMap {
468
468
  }
469
469
  const ret = await this._writeRegisters(47415, dataType.numToArray(event.value, dataType.uint16));
470
470
  if (ret) {
471
- this.inverterInfo.instance.stateCache.set(`${this.inverterInfo.path}.${event.id}`, event.value);
471
+ this.inverterInfo.instance.stateCache.set(`${this.inverterInfo.path}.${event.id}`, event.value, { type: 'number' });
472
472
  }
473
473
  return ret;
474
474
  },
@@ -544,6 +544,20 @@ class ServiceQueueMap {
544
544
  }
545
545
  }
546
546
 
547
+ /**
548
+ * @description Check if the value of the event is a number and optionally round it to the nearest integer.
549
+ * @param {object} event The event object
550
+ * @param {boolean} [round] If true, the value is rounded to the nearest integer
551
+ * @returns {boolean} True if the value is a number, false otherwise
552
+ */
553
+ isNumber(event, round = true) {
554
+ if (isNaN(event.value)) {
555
+ return false;
556
+ }
557
+ if (round) event.value = Math.round(event.value);
558
+ return true;
559
+ }
560
+
547
561
  get(id) {
548
562
  return this._eventMap.get(id);
549
563
  }
@@ -552,7 +566,7 @@ class ServiceQueueMap {
552
566
  const service = this._serviceMap.get(id);
553
567
  if (state && service) {
554
568
  if (state.val !== null && !state.ack) {
555
- this.log.info(`Control: Event - state: ${id} changed: ${state.val} ack: ${state.ack}`);
569
+ this.log.info(`Control: Event - state: ${this.inverterInfo.path}.control.${id} changed: ${state.val} ack: ${state.ack}`);
556
570
  const event = this._eventMap.get(id);
557
571
  if (event) {
558
572
  event.value = state.val;
@@ -563,16 +577,25 @@ class ServiceQueueMap {
563
577
  }
564
578
  }
565
579
  }
566
-
580
+ /*
567
581
  values() {
568
582
  return this._map.values();
569
583
  }
570
-
584
+ */
571
585
  /**
572
- * Processes the events in the eventMap. For each event, it calls the associated function in the serviceMap.
586
+ * Processes pending events in the service queue and attempts to execute their associated functions.
587
+ *
588
+ * @async
589
+ * @param {object} modbusClient - The modbus client instance used for communication.
573
590
  *
574
- * @param modbusClient - The modbus client to use for writing the states.
591
+ * @description This function iterates over the events in the service queue, checking whether each event
592
+ * can be processed. It verifies conditions such as battery presence and running status, and checks if the
593
+ * event value is a number when required. If the event is successfully processed, it acknowledges the event
594
+ * by setting its state in the adapter and removes it from the event map. If the event cannot be processed
595
+ * after multiple attempts, it is discarded. The function initializes the service queue if it is not already
596
+ * initialized and the adapter is connected.
575
597
  */
598
+
576
599
  async process(modbusClient) {
577
600
  //if (!this.inverterInfo.instance.modbusAllowed) return;
578
601
  this._modbusClient = modbusClient;
@@ -609,6 +632,15 @@ class ServiceQueueMap {
609
632
  continue;
610
633
  }
611
634
  }
635
+ if (service.state.type === 'number') {
636
+ if (!this.isNumber(event)) {
637
+ this.log.warn(
638
+ `Control: Event is discarded because the value ${event.value} is not a number. State: ${this.inverterInfo.path}.control.${event.id}`,
639
+ );
640
+ this._eventMap.delete(event.id); //forget the event
641
+ continue;
642
+ }
643
+ }
612
644
  count++;
613
645
  if (await service.fn(event)) {
614
646
  service.errorCount = 0;
@@ -633,7 +665,6 @@ class ServiceQueueMap {
633
665
  } //max 2 Events
634
666
  }
635
667
  }
636
- //if (!this._initialized && this.adapter.isReady) await this._init();
637
668
  if (!this._initialized && this.adapter.isConnected) {
638
669
  await this._init();
639
670
  }
@@ -21,7 +21,6 @@ class DriverBase {
21
21
  this._deviceStatus = -1; //device shutdown or standby
22
22
  this._regMap = new RegisterMap();
23
23
 
24
- //v0.6.x
25
24
  this.control = null; //Battery Charge control
26
25
  this.log = new Logging(this.adapter); //my own Logger
27
26
 
@@ -1114,11 +1114,11 @@ class InverterSun2000 extends DriverBase {
1114
1114
  register: { reg: 32090, type: dataType.uint16 },
1115
1115
  },
1116
1116
  {
1117
- state: { id: 'startupTime', name: 'Startup time', type: 'number', unit: '', role: 'value.time', desc: 'reg:32091, len:2' },
1117
+ state: { id: 'startupTime', name: 'Startup time', type: 'number', unit: '', role: 'value', desc: 'reg:32091, len:2' },
1118
1118
  register: { reg: 32091, type: dataType.uint32 },
1119
1119
  },
1120
1120
  {
1121
- state: { id: 'shutdownTime', name: 'Shutdown time', type: 'number', unit: '', role: 'value.time', desc: 'reg:32093, len:2' },
1121
+ state: { id: 'shutdownTime', name: 'Shutdown time', type: 'number', unit: '', role: 'value', desc: 'reg:32093, len:2' },
1122
1122
  register: { reg: 32093, type: dataType.uint32 },
1123
1123
  },
1124
1124
  {
@@ -1143,7 +1143,27 @@ class InverterSun2000 extends DriverBase {
1143
1143
  },
1144
1144
  register: { reg: 32114, type: dataType.uint32, gain: 100 },
1145
1145
  },
1146
+ {
1147
+ state: { id: 'derived.shutdownTime', name: 'shutdown time', type: 'number', unit: '', role: 'value.time', desc: 'fixed time' },
1148
+ },
1149
+ {
1150
+ state: { id: 'derived.startupTime', name: 'startup time', type: 'number', unit: '', role: 'value.time', desc: 'fixed time' },
1151
+ },
1146
1152
  ],
1153
+ postHook: path => {
1154
+ function fixTime(value) {
1155
+ if (value > 0) {
1156
+ value = value * 1000;
1157
+ const offset = new Date(value).getTimezoneOffset();
1158
+ value += offset * 60000;
1159
+ }
1160
+ return value;
1161
+ }
1162
+ const shutdown = this.stateCache.get(`${path}shutdownTime`)?.value;
1163
+ this.stateCache.set(`${path}derived.shutdownTime`, fixTime(shutdown), { type: 'number' });
1164
+ const startup = this.stateCache.get(`${path}startupTime`)?.value;
1165
+ this.stateCache.set(`${path}derived.startupTime`, fixTime(startup), { type: 'number' });
1166
+ },
1147
1167
  },
1148
1168
  {
1149
1169
  address: 37100,
@@ -1372,6 +1392,26 @@ class InverterSun2000 extends DriverBase {
1372
1392
  };
1373
1393
 
1374
1394
  const newHooks = [
1395
+ {
1396
+ //activePower adjust
1397
+ refresh: dataRefreshRate.high,
1398
+ fn: path => {
1399
+ const chargePower = this.stateCache.get(`${path}battery.chargeDischargePower`)?.value ?? 0;
1400
+ const inputPower = this.stateCache.get(`${path}inputPower`)?.value ?? 0;
1401
+ const activePower = this.stateCache.get(`${path}activePower`)?.value ?? 0;
1402
+ if (Math.abs(activePower - inputPower + chargePower) > 0) {
1403
+ this.log.debug(`activePower ${activePower} !== inputPower ${inputPower}-chargePower ${chargePower}`);
1404
+ this.stateCache.set(`${path}activePower`, inputPower - chargePower, { type: 'number' });
1405
+ /*
1406
+ if (chargePower === 0) {
1407
+ this.stateCache.set(`${path}activePower`, inputPower - chargePower, { type: 'number' });
1408
+ } else {
1409
+ this.stateCache.set(`${path}battery.chargeDischargePower`, inputPower - activePower, { type: 'number' });
1410
+ }
1411
+ */
1412
+ }
1413
+ },
1414
+ },
1375
1415
  {
1376
1416
  refresh: dataRefreshRate.low,
1377
1417
  state: {
@@ -21,7 +21,7 @@ class Sdongle extends DriverBase {
21
21
  },
22
22
  {
23
23
  state: { id: 'sdongle.OSVersion', name: 'OS version', type: 'string', unit: '', role: 'value', desc: 'reg:30050, len:15' },
24
- register: { reg: 30050, type: dataType.string, length: 8 },
24
+ register: { reg: 30050, type: dataType.string, length: 9 },
25
25
  },
26
26
  {
27
27
  state: { id: 'sdongle.protokolVersion', name: 'Protokol version', type: 'number', unit: '', role: 'value', desc: 'reg:30068, len:2' },
package/lib/register.js CHANGED
@@ -41,28 +41,67 @@ class Registers {
41
41
  unit: 'kW',
42
42
  role: 'value.power',
43
43
  },
44
+ { id: 'collected.usableSurplusPower', name: 'usable surplus power', type: 'number', unit: 'kW', role: 'value.power' },
44
45
  ],
45
46
  fn: inverters => {
46
- let sum = 0;
47
+ let actPower = 0;
47
48
  let inPower = 0;
48
49
  let inPowerEff = 0;
49
50
  let chargeDischarge = 0;
51
+
52
+ function calcUsableSurplus() {
53
+ let surplusPower = meterPower;
54
+ if (this.adapter.control) {
55
+ const minSoc = this.adapter.control.get('usableSurplus.minSoc')?.value ?? 0;
56
+ const bufferSoc = this.adapter.control.get('usableSurplus.bufferSoc')?.value ?? 0;
57
+ const residualPower = this.adapter.control.get('usableSurplus.residualPower')?.value ?? 0;
58
+ const soc = this.stateCache.get('collected.SOC')?.value ?? 0;
59
+
60
+ if (soc >= minSoc) {
61
+ surplusPower += chargeDischarge;
62
+ if (soc < bufferSoc || bufferSoc === 0) {
63
+ surplusPower -= residualPower / 1000;
64
+ }
65
+ }
66
+ if (soc >= bufferSoc && bufferSoc > 0) {
67
+ const bufferPower = this.adapter.control.get('usableSurplus.bufferPower')?.value ?? 0;
68
+ surplusPower += bufferPower / 1000;
69
+ }
70
+
71
+ //this.adapter.log.info(`### TEST usableSurplus.minSoc ${minSoc}`);
72
+ }
73
+ if (surplusPower < 0.1) surplusPower = 0;
74
+ return surplusPower;
75
+ }
76
+
50
77
  for (const inverter of inverters) {
51
- //v0.4.x
52
78
  if (inverter.driverClass != driverClasses.inverter) {
53
79
  continue;
54
80
  }
55
- sum += this.stateCache.get(`${inverter.path}.activePower`)?.value ?? 0;
81
+ actPower += this.stateCache.get(`${inverter.path}.activePower`)?.value ?? 0;
56
82
  inPower += this.stateCache.get(`${inverter.path}.inputPower`)?.value ?? 0;
57
83
  inPowerEff += this.stateCache.get(`${inverter.path}.derived.inputPowerWithEfficiencyLoss`)?.value ?? 0;
58
84
  chargeDischarge += this.stateCache.get(`${inverter.path}.battery.chargeDischargePower`)?.value ?? 0;
59
85
  }
86
+
87
+ const meterPower = this.stateCache.get('meter.activePower')?.value ?? 0;
88
+
89
+ //Zu geringe Erzeugerenegie
90
+ let houseConsum = actPower - meterPower;
91
+ if (houseConsum < 0) {
92
+ houseConsum = 0;
93
+ }
94
+ //Überschuss (Differenz)
95
+ const surplusPower = calcUsableSurplus.bind(this)();
96
+
60
97
  this.stateCache.set('collected.inputPower', inPower, { type: 'number', renew: true });
61
98
  this.stateCache.set('collected.inputPowerWithEfficiencyLoss', inPowerEff, { type: 'number' });
62
- this.stateCache.set('collected.activePower', sum, { type: 'number', renew: true });
63
- sum -= this.stateCache.get('meter.activePower')?.value;
64
- this.stateCache.set('collected.houseConsumption', sum, { type: 'number' });
99
+ this.stateCache.set('collected.activePower', actPower, { type: 'number', renew: true });
100
+ this.stateCache.set('collected.houseConsumption', houseConsum, { type: 'number' });
65
101
  this.stateCache.set('collected.chargeDischargePower', chargeDischarge, { type: 'number' });
102
+ this.stateCache.set('collected.usableSurplusPower', surplusPower, {
103
+ type: 'number',
104
+ });
66
105
  },
67
106
  },
68
107
  {
@@ -131,6 +170,7 @@ class Registers {
131
170
  let totalCharge = 0;
132
171
  let ratedCap = 0;
133
172
  let load = 0;
173
+
134
174
  for (const inverter of inverters) {
135
175
  //v0.4.x
136
176
  if (inverter.driverClass != driverClasses.inverter) {
@@ -151,6 +191,7 @@ class Registers {
151
191
  ratedCap += this.stateCache.get(`${inverter.path}.battery.ratedCapacity`)?.value;
152
192
  }
153
193
  }
194
+
154
195
  this.stateCache.set('collected.dailyEnergyYield', outYield, { type: 'number' });
155
196
  this.stateCache.set('collected.dailyInputYield', inYield, { type: 'number' }); //deprecated
156
197
  this.stateCache.set('collected.dailySolarYield', solarYield, { type: 'number' });
package/lib/tools.js CHANGED
@@ -59,6 +59,12 @@ class StateMap {
59
59
  this.stateMap = new Map();
60
60
  }
61
61
 
62
+ round(num, decimalPlaces = 0) {
63
+ var p = Math.pow(10, decimalPlaces);
64
+ var n = num * p * (1 + Number.EPSILON);
65
+ return Math.round(n) / p;
66
+ }
67
+
62
68
  get(id) {
63
69
  return this.stateMap.get(id);
64
70
  }
@@ -69,7 +75,8 @@ class StateMap {
69
75
  }
70
76
  if (value !== null) {
71
77
  if (options?.type == 'number') {
72
- value = Math.round((value + Number.EPSILON) * 1000) / 1000; //3rd behind
78
+ //value = Math.round((value + Number.EPSILON) * 1000) / 1000; //3rd behind
79
+ value = this.round(value, 3);
73
80
  }
74
81
 
75
82
  const existing = this.get(id); //existing entry
package/main.js CHANGED
@@ -1,5 +1,4 @@
1
1
  'use strict';
2
-
3
2
  /*
4
3
  * Created with @iobroker/create-adapter v2.5.0
5
4
  */
@@ -14,6 +13,7 @@ const ModbusConnect = require(`${__dirname}/lib/modbus/modbus_connect.js`);
14
13
  const ModbusServer = require(`${__dirname}/lib/modbus/modbus_server.js`);
15
14
  const { driverClasses, dataRefreshRate } = require(`${__dirname}/lib/types.js`);
16
15
  const { Logging, getAstroDate, isSunshine } = require(`${__dirname}/lib/tools.js`);
16
+ const ConfigMap = require(`${__dirname}/lib/controls/config_map.js`);
17
17
 
18
18
  class Sun2000 extends utils.Adapter {
19
19
  /**
@@ -30,8 +30,7 @@ class Sun2000 extends utils.Adapter {
30
30
  this.lastStateUpdatedHigh = 0;
31
31
  this.lastStateUpdatedLow = 0;
32
32
  this.isConnected = false;
33
- this.isReady = false; //v0.8.x
34
-
33
+ this.isReady = false;
35
34
  this.devices = [];
36
35
  this.settings = {
37
36
  highInterval: 20000,
@@ -67,6 +66,9 @@ class Sun2000 extends utils.Adapter {
67
66
  //v0.6.
68
67
  this.logger = new Logging(this); //only for adapter
69
68
 
69
+ //1.1.0
70
+ this.control = new ConfigMap(this);
71
+
70
72
  this.on('ready', this.onReady.bind(this));
71
73
  this.on('stateChange', this.onStateChange.bind(this));
72
74
  // this.on('objectChange', this.onObjectChange.bind(this));
@@ -75,6 +77,14 @@ class Sun2000 extends utils.Adapter {
75
77
  }
76
78
 
77
79
  async initPath() {
80
+ await this.extendObject('control', {
81
+ type: 'channel',
82
+ common: {
83
+ name: 'channel control',
84
+ },
85
+ native: {},
86
+ });
87
+
78
88
  //inverter
79
89
  await this.extendObject('meter', {
80
90
  type: 'device',
@@ -194,6 +204,8 @@ class Sun2000 extends utils.Adapter {
194
204
 
195
205
  async StartProcess() {
196
206
  await this.initPath();
207
+ await this.control.init();
208
+
197
209
  this.state = new Registers(this);
198
210
  await this.atMidnight();
199
211
  if (this.settings.modbusAdjust) {
@@ -579,8 +591,12 @@ class Sun2000 extends utils.Adapter {
579
591
  onStateChange(id, state) {
580
592
  if (state) {
581
593
  // The state was changed
582
- // sun2000.0.inverter.0.control
594
+ if (state.ack) {
595
+ //this.logger.info(`state ${id} was changed but the ack flag was set. Therefore, no processing takes place!`);
596
+ return;
597
+ }
583
598
  const idArray = id.split('.');
599
+ // sun2000.0.inverter.0.control
584
600
  if (idArray[2] == 'inverter') {
585
601
  const control = this.devices[Number(idArray[3])].instance.control;
586
602
  if (control) {
@@ -592,6 +608,15 @@ class Sun2000 extends utils.Adapter {
592
608
  }
593
609
  //this.log.info(`### state ${id} changed: ${state.val} (ack = ${state.ack})`);
594
610
  }
611
+ // sun2000.0.inverter.0.config
612
+ if (idArray[2] == 'control') {
613
+ let serviceId = idArray[3];
614
+ for (let i = 4; i < idArray.length; i++) {
615
+ serviceId += `.${idArray[i]}`;
616
+ }
617
+ //this.log.info(`### id: ${serviceId} state ${id} changed: ${state.val} (ack = ${state.ack})`);
618
+ this.control.set(serviceId, state);
619
+ }
595
620
  } else {
596
621
  // The state was deleted
597
622
  this.logger.info(`state ${id} deleted`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "iobroker.sun2000",
3
- "version": "1.0.1",
3
+ "version": "1.2.2-alpha.0",
4
4
  "description": "sun2000",
5
5
  "author": {
6
6
  "name": "bolliy",
@@ -28,7 +28,7 @@
28
28
  },
29
29
  "dependencies": {
30
30
  "@iobroker/adapter-core": "^3.2.3",
31
- "modbus-serial": "^8.0.18",
31
+ "modbus-serial": "^8.0.20",
32
32
  "suncalc2": "^1.8.1",
33
33
  "tcp-port-used": "^1.0.2"
34
34
  },
@@ -37,16 +37,16 @@
37
37
  "@alcalzone/release-script-plugin-iobroker": "^3.7.2",
38
38
  "@alcalzone/release-script-plugin-license": "^3.7.0",
39
39
  "@alcalzone/release-script-plugin-manual-review": "^3.7.0",
40
- "@eslint/eslintrc": "^3.3.0",
40
+ "@eslint/eslintrc": "^3.3.1",
41
41
  "@eslint/js": "^9.20.0",
42
42
  "@iobroker/adapter-dev": "^1.4.0",
43
- "@iobroker/eslint-config": "^1.0.0",
44
- "@iobroker/testing": "^5.0.3",
45
- "@tsconfig/node20": "^20.1.4",
43
+ "@iobroker/eslint-config": "^1.0.1",
44
+ "@iobroker/testing": "^5.0.4",
45
+ "@tsconfig/node20": "^20.1.5",
46
46
  "@types/chai": "^4.3.20",
47
47
  "@types/chai-as-promised": "^7.1.8",
48
48
  "@types/mocha": "^10.0.10",
49
- "@types/node": "^22.13.8",
49
+ "@types/node": "^22.13.16",
50
50
  "@types/proxyquire": "^1.3.31",
51
51
  "@types/sinon": "^17.0.4",
52
52
  "@types/sinon-chai": "^3.2.12",
@@ -55,7 +55,7 @@
55
55
  "globals": "^15.15.0",
56
56
  "mocha": "^11.1.0",
57
57
  "proxyquire": "^2.1.3",
58
- "sinon": "^19.0.2",
58
+ "sinon": "^19.0.5",
59
59
  "sinon-chai": "^3.7.0",
60
60
  "typescript": "~5.7.3"
61
61
  },