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 +18 -1
- package/io-package.json +53 -53
- package/lib/controls/config_map.js +187 -0
- package/lib/controls/service_queue.js +48 -17
- package/lib/drivers/driver_base.js +0 -1
- package/lib/drivers/driver_inverter.js +42 -2
- package/lib/drivers/driver_sdongle.js +1 -1
- package/lib/register.js +47 -6
- package/lib/tools.js +8 -1
- package/main.js +29 -4
- package/package.json +8 -8
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
|
|
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
|
|
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
|
|
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
|
-
* @
|
|
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
|
}
|
|
@@ -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
|
|
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
|
|
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:
|
|
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
|
|
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
|
-
|
|
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',
|
|
63
|
-
|
|
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;
|
|
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
|
-
|
|
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
|
|
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.
|
|
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.
|
|
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.
|
|
44
|
-
"@iobroker/testing": "^5.0.
|
|
45
|
-
"@tsconfig/node20": "^20.1.
|
|
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.
|
|
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.
|
|
58
|
+
"sinon": "^19.0.5",
|
|
59
59
|
"sinon-chai": "^3.7.0",
|
|
60
60
|
"typescript": "~5.7.3"
|
|
61
61
|
},
|