iobroker.goodwe-sems 0.1.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.
@@ -0,0 +1,43 @@
1
+ {
2
+ "debugRawResponse": "Сохранять сырой JSON-ответ (отладка)",
3
+ "debugRawResponseHelp": "Записывает последний полный ответ SEMS API в info.rawResponse. По умолчанию выключено.",
4
+ "hintLoginText": "Войдите с обычной учётной записью портала SEMS (той же, что на semsportal.com или в приложении SEMS). Учётная запись SEMS \"Organization\"/OpenAPI не требуется.",
5
+ "hintPollingText": "Портал SEMS не документирован для сторонних обращений и может ограничивать частые запросы. 5 минут - безопасное значение по умолчанию; адаптер применяет минимум 60с и автоматически снижает частоту при ошибках.",
6
+ "hintPushoverText": "Критические события можно отправлять через Pushover в дополнение к логу ioBroker.",
7
+ "maxConsecutiveErrors": "Ошибок до уведомления \"станция недоступна\"",
8
+ "maxConsecutiveErrorsHelp": "Количество последовательных неудачных опросов перед уведомлением об отключении.",
9
+ "notifyHeader": "Надсилати сповіщення при",
10
+ "notifyOnAdapterError": "Неочікуваній помилці адаптера",
11
+ "notifyOnLoginFailure": "Помилка входу SEMS",
12
+ "notifyOnRateLimit": "Досягнуто ліміту запитів SEMS",
13
+ "notifyOnStationOffline": "Тривалій відмові",
14
+ "pollInterval": "Інтервал опитування (секунди)",
15
+ "pollIntervalHelp": "Минимум 60с, рекомендуется 300с (5 мин).",
16
+ "pollingTab": "Опитування",
17
+ "powerStationId": "ID электростанции (опционально)",
18
+ "powerStationIdHelp": "Оставьте пустым для автоматического определения. При нескольких станциях укажите ID вручную (из URL портала SEMS после входа).",
19
+ "priorityEmergency": "Экстренный",
20
+ "priorityHigh": "Высокий",
21
+ "priorityLow": "Низкий (без звука)",
22
+ "priorityNormal": "Обычный",
23
+ "pushoverApiToken": "Pushover API/App-токен",
24
+ "pushoverInstance": "Экземпляр адаптера Pushover",
25
+ "pushoverInstanceHelp": "например, pushover.0",
26
+ "pushoverMode": "Доставка Pushover",
27
+ "pushoverModeBoth": "Оба варианта",
28
+ "pushoverModeDirect": "Напрямую через API Pushover",
29
+ "pushoverModeHelp": "Способ отправки уведомлений Pushover.",
30
+ "pushoverModeInstance": "Через существующий экземпляр ioBroker.pushover",
31
+ "pushoverModeNone": "Отключено (только лог)",
32
+ "pushoverPriority": "Приоритет Pushover",
33
+ "pushoverTab": "Сповіщення Pushover",
34
+ "pushoverUserKey": "Pushover User-Key",
35
+ "requestTimeout": "Тайм-аут запроса (секунды)",
36
+ "requestTimeoutHelp": "На один HTTP-запрос к порталу SEMS.",
37
+ "semsAccount": "Обліковий запис SEMS (e-mail)",
38
+ "semsAccountHelp": "Адрес электронной почты для входа на semsportal.com.",
39
+ "semsPassword": "Пароль SEMS",
40
+ "semsPasswordHelp": "Хранится в зашифрованном виде.",
41
+ "stationOfflineMinutes": "Пауза уведомления об отключении (мин)",
42
+ "stationOfflineMinutesHelp": "Только информативно."
43
+ }
@@ -0,0 +1,43 @@
1
+ {
2
+ "debugRawResponse": "保存原始 JSON 响应(调试)",
3
+ "debugRawResponseHelp": "将最近一次完整的 SEMS API 响应写入 info.rawResponse,默认关闭。",
4
+ "hintLoginText": "使用您普通的 SEMS Portal 账户登录(与 semsportal.com 或 SEMS App 相同的账户)。不需要 SEMS \"organization\"/OpenAPI 账户。",
5
+ "hintPollingText": "SEMS 门户未公开第三方使用文档,频繁请求可能触发限流。默认 5 分钟较为安全;适配器强制最小值为 60 秒,并在出错或被限流后自动退避。",
6
+ "hintPushoverText": "除始终写入 ioBroker 日志外,关键事件还可通过 Pushover 推送到手机。",
7
+ "maxConsecutiveErrors": "触发“电站离线”告警前的失败次数",
8
+ "maxConsecutiveErrorsHelp": "连续失败的轮询次数达到该值后,视为真正的故障。",
9
+ "notifyHeader": "以下情况发送告警",
10
+ "notifyOnAdapterError": "适配器发生意外错误",
11
+ "notifyOnLoginFailure": "SEMS 登录失败",
12
+ "notifyOnRateLimit": "达到 SEMS 请求限制",
13
+ "notifyOnStationOffline": "长时间故障",
14
+ "pollInterval": "轮询间隔(秒)",
15
+ "pollIntervalHelp": "最小 60 秒,建议 300 秒(5 分钟)。",
16
+ "pollingTab": "轮询间隔",
17
+ "powerStationId": "电站 ID(可选)",
18
+ "powerStationIdHelp": "留空以自动检测。若账户下有多个电站,请手动填写 ID(登录后可在 SEMS 门户网址中找到)。",
19
+ "priorityEmergency": "紧急",
20
+ "priorityHigh": "高",
21
+ "priorityLow": "低(无声音)",
22
+ "priorityNormal": "普通",
23
+ "pushoverApiToken": "Pushover API/App 令牌",
24
+ "pushoverInstance": "Pushover 适配器实例",
25
+ "pushoverInstanceHelp": "例如 pushover.0",
26
+ "pushoverMode": "Pushover 发送方式",
27
+ "pushoverModeBoth": "两者皆用",
28
+ "pushoverModeDirect": "直接调用 Pushover API",
29
+ "pushoverModeHelp": "Pushover 通知的发送方式。",
30
+ "pushoverModeInstance": "通过现有的 ioBroker.pushover 实例",
31
+ "pushoverModeNone": "禁用(仅记录日志)",
32
+ "pushoverPriority": "Pushover 优先级",
33
+ "pushoverTab": "Pushover 通知",
34
+ "pushoverUserKey": "Pushover User-Key",
35
+ "requestTimeout": "请求超时(秒)",
36
+ "requestTimeoutHelp": "每次向 SEMS 门户发起请求的超时时间。",
37
+ "semsAccount": "SEMS 账户(邮箱)",
38
+ "semsAccountHelp": "用于登录 semsportal.com 的电子邮件地址。",
39
+ "semsPassword": "SEMS 密码",
40
+ "semsPasswordHelp": "以加密方式存储。",
41
+ "stationOfflineMinutes": "故障告警间隔(分钟)",
42
+ "stationOfflineMinutesHelp": "仅作说明用途。"
43
+ }
@@ -0,0 +1,211 @@
1
+ {
2
+ "i18n": true,
3
+ "type": "tabs",
4
+ "items": {
5
+ "_login": {
6
+ "type": "panel",
7
+ "label": "SEMS-Login",
8
+ "items": {
9
+ "_hintLogin": {
10
+ "type": "staticText",
11
+ "text": "hintLoginText",
12
+ "newLine": true
13
+ },
14
+ "account": {
15
+ "type": "text",
16
+ "label": "semsAccount",
17
+ "help": "semsAccountHelp",
18
+ "newLine": true,
19
+ "trim": true,
20
+ "xs": 12,
21
+ "sm": 6,
22
+ "md": 4
23
+ },
24
+ "password": {
25
+ "type": "password",
26
+ "label": "semsPassword",
27
+ "help": "semsPasswordHelp",
28
+ "xs": 12,
29
+ "sm": 6,
30
+ "md": 4
31
+ },
32
+ "powerStationId": {
33
+ "type": "text",
34
+ "label": "powerStationId",
35
+ "help": "powerStationIdHelp",
36
+ "newLine": true,
37
+ "trim": true,
38
+ "xs": 12,
39
+ "sm": 8,
40
+ "md": 6
41
+ }
42
+ }
43
+ },
44
+ "_polling": {
45
+ "type": "panel",
46
+ "label": "pollingTab",
47
+ "items": {
48
+ "_hintPolling": {
49
+ "type": "staticText",
50
+ "text": "hintPollingText",
51
+ "newLine": true
52
+ },
53
+ "pollInterval": {
54
+ "type": "number",
55
+ "label": "pollInterval",
56
+ "help": "pollIntervalHelp",
57
+ "min": 60,
58
+ "max": 3600,
59
+ "step": 30,
60
+ "newLine": true,
61
+ "xs": 12,
62
+ "sm": 6,
63
+ "md": 3
64
+ },
65
+ "requestTimeout": {
66
+ "type": "number",
67
+ "label": "requestTimeout",
68
+ "help": "requestTimeoutHelp",
69
+ "min": 5,
70
+ "max": 60,
71
+ "xs": 12,
72
+ "sm": 6,
73
+ "md": 3
74
+ },
75
+ "maxConsecutiveErrors": {
76
+ "type": "number",
77
+ "label": "maxConsecutiveErrors",
78
+ "help": "maxConsecutiveErrorsHelp",
79
+ "min": 1,
80
+ "max": 20,
81
+ "newLine": true,
82
+ "xs": 12,
83
+ "sm": 6,
84
+ "md": 3
85
+ },
86
+ "stationOfflineMinutes": {
87
+ "type": "number",
88
+ "label": "stationOfflineMinutes",
89
+ "help": "stationOfflineMinutesHelp",
90
+ "min": 5,
91
+ "max": 720,
92
+ "xs": 12,
93
+ "sm": 6,
94
+ "md": 3
95
+ },
96
+ "debugRawResponse": {
97
+ "type": "checkbox",
98
+ "label": "debugRawResponse",
99
+ "help": "debugRawResponseHelp",
100
+ "newLine": true,
101
+ "xs": 12,
102
+ "sm": 8,
103
+ "md": 6
104
+ }
105
+ }
106
+ },
107
+ "_pushover": {
108
+ "type": "panel",
109
+ "label": "pushoverTab",
110
+ "items": {
111
+ "_hintPushover": {
112
+ "type": "staticText",
113
+ "text": "hintPushoverText",
114
+ "newLine": true
115
+ },
116
+ "pushoverMode": {
117
+ "type": "select",
118
+ "label": "pushoverMode",
119
+ "help": "pushoverModeHelp",
120
+ "newLine": true,
121
+ "options": [
122
+ { "value": "none", "label": "pushoverModeNone" },
123
+ { "value": "instance", "label": "pushoverModeInstance" },
124
+ { "value": "direct", "label": "pushoverModeDirect" },
125
+ { "value": "both", "label": "pushoverModeBoth" }
126
+ ],
127
+ "xs": 12,
128
+ "sm": 6,
129
+ "md": 4
130
+ },
131
+ "pushoverPriority": {
132
+ "type": "select",
133
+ "label": "pushoverPriority",
134
+ "newLine": true,
135
+ "options": [
136
+ { "value": -1, "label": "priorityLow" },
137
+ { "value": 0, "label": "priorityNormal" },
138
+ { "value": 1, "label": "priorityHigh" },
139
+ { "value": 2, "label": "priorityEmergency" }
140
+ ],
141
+ "xs": 12,
142
+ "sm": 6,
143
+ "md": 4
144
+ },
145
+ "pushoverInstance": {
146
+ "type": "instance",
147
+ "label": "pushoverInstance",
148
+ "help": "pushoverInstanceHelp",
149
+ "adapter": "pushover",
150
+ "newLine": true,
151
+ "hidden": "data.pushoverMode !== 'instance' && data.pushoverMode !== 'both'",
152
+ "xs": 12,
153
+ "sm": 8,
154
+ "md": 6
155
+ },
156
+ "pushoverUserKey": {
157
+ "type": "text",
158
+ "label": "pushoverUserKey",
159
+ "newLine": true,
160
+ "hidden": "data.pushoverMode !== 'direct' && data.pushoverMode !== 'both'",
161
+ "xs": 12,
162
+ "sm": 6,
163
+ "md": 4
164
+ },
165
+ "pushoverApiToken": {
166
+ "type": "password",
167
+ "label": "pushoverApiToken",
168
+ "hidden": "data.pushoverMode !== 'direct' && data.pushoverMode !== 'both'",
169
+ "xs": 12,
170
+ "sm": 6,
171
+ "md": 4
172
+ },
173
+ "_notifyHeader": {
174
+ "type": "header",
175
+ "text": "notifyHeader",
176
+ "newLine": true
177
+ },
178
+ "notifyOnLoginFailure": {
179
+ "type": "checkbox",
180
+ "label": "notifyOnLoginFailure",
181
+ "newLine": true,
182
+ "xs": 12,
183
+ "sm": 6,
184
+ "md": 3
185
+ },
186
+ "notifyOnRateLimit": {
187
+ "type": "checkbox",
188
+ "label": "notifyOnRateLimit",
189
+ "xs": 12,
190
+ "sm": 6,
191
+ "md": 3
192
+ },
193
+ "notifyOnStationOffline": {
194
+ "type": "checkbox",
195
+ "label": "notifyOnStationOffline",
196
+ "newLine": true,
197
+ "xs": 12,
198
+ "sm": 6,
199
+ "md": 3
200
+ },
201
+ "notifyOnAdapterError": {
202
+ "type": "checkbox",
203
+ "label": "notifyOnAdapterError",
204
+ "xs": 12,
205
+ "sm": 6,
206
+ "md": 3
207
+ }
208
+ }
209
+ }
210
+ }
211
+ }
@@ -0,0 +1,207 @@
1
+ {
2
+ "common": {
3
+ "name": "goodwe-sems",
4
+ "version": "0.1.0",
5
+ "news": {
6
+ "0.1.0": {
7
+ "en": "initial release",
8
+ "de": "Erstveröffentlichung",
9
+ "ru": "первый выпуск",
10
+ "pt": "lançamento inicial",
11
+ "nl": "eerste release",
12
+ "fr": "version initiale",
13
+ "it": "versione iniziale",
14
+ "es": "versión inicial",
15
+ "pl": "pierwsze wydanie",
16
+ "uk": "перший випуск",
17
+ "zh-cn": "首次发布"
18
+ }
19
+ },
20
+ "title": "GoodWe SEMS Portal",
21
+ "titleLang": {
22
+ "en": "GoodWe SEMS Portal",
23
+ "de": "GoodWe SEMS Portal",
24
+ "ru": "GoodWe SEMS Portal",
25
+ "pt": "GoodWe SEMS Portal",
26
+ "nl": "GoodWe SEMS Portal",
27
+ "fr": "GoodWe SEMS Portal",
28
+ "it": "GoodWe SEMS Portal",
29
+ "es": "GoodWe SEMS Portal",
30
+ "pl": "GoodWe SEMS Portal",
31
+ "uk": "GoodWe SEMS Portal",
32
+ "zh-cn": "GoodWe SEMS 门户"
33
+ },
34
+ "desc": {
35
+ "en": "Reads inverter, battery and power-flow data from the GoodWe SEMS Portal cloud for installations that have no local/LAN access to the inverter.",
36
+ "de": "Liest Wechselrichter-, Batterie- und Energiefluss-Daten aus dem GoodWe-SEMS-Portal (Cloud) für Anlagen ohne lokalen/LAN-Zugriff auf den Wechselrichter.",
37
+ "ru": "Считывает данные инвертора, батареи и потоков энергии из облачного портала GoodWe SEMS для установок без локального доступа к инвертору.",
38
+ "pt": "Lê dados do inversor, bateria e fluxo de energia do portal SEMS da GoodWe na nuvem para instalações sem acesso local/LAN ao inversor.",
39
+ "nl": "Leest omvormer-, batterij- en energiestroomgegevens uit het GoodWe SEMS-portaal (cloud) voor installaties zonder lokale/LAN-toegang tot de omvormer.",
40
+ "fr": "Lit les données de l'onduleur, de la batterie et des flux d'énergie depuis le portail cloud GoodWe SEMS pour les installations sans accès local/LAN à l'onduleur.",
41
+ "it": "Legge i dati dell'inverter, della batteria e del flusso di energia dal portale cloud GoodWe SEMS per impianti senza accesso locale/LAN all'inverter.",
42
+ "es": "Lee datos del inversor, la batería y el flujo de energía desde el portal en la nube GoodWe SEMS para instalaciones sin acceso local/LAN al inversor.",
43
+ "pl": "Odczytuje dane falownika, baterii i przepływu energii z chmury portalu GoodWe SEMS dla instalacji bez lokalnego dostępu do falownika.",
44
+ "uk": "Зчитує дані інвертора, батареї та потоків енергії з хмарного порталу GoodWe SEMS для установок без локального доступу до інвертора.",
45
+ "zh-cn": "为无法本地访问逆变器的装置从 GoodWe SEMS 云端门户读取逆变器、电池和功率流数据。"
46
+ },
47
+ "authors": [
48
+ "bueste <bueste@users.noreply.github.com>"
49
+ ],
50
+ "keywords": ["goodwe", "sems", "semsportal", "inverter", "solar", "pv", "photovoltaik", "battery"],
51
+ "licenseInformation": {
52
+ "type": "free",
53
+ "license": "MIT"
54
+ },
55
+ "platform": "Javascript/Node.js",
56
+ "main": "main.js",
57
+ "icon": "goodwe-sems.png",
58
+ "enabled": true,
59
+ "extIcon": "https://raw.githubusercontent.com/bueste/ioBroker.goodwe-sems/main/admin/goodwe-sems.png",
60
+ "readme": "https://github.com/bueste/ioBroker.goodwe-sems/blob/main/README.md",
61
+ "loglevel": "info",
62
+ "mode": "daemon",
63
+ "type": "energy",
64
+ "compact": true,
65
+ "connectionType": "cloud",
66
+ "dataSource": "poll",
67
+ "tier": 3,
68
+ "adminUI": {
69
+ "config": "json"
70
+ },
71
+ "encryptedNative": ["password", "pushoverApiToken"],
72
+ "protectedNative": ["password", "pushoverApiToken"],
73
+ "dependencies": [
74
+ {
75
+ "js-controller": ">=5.0.19"
76
+ }
77
+ ],
78
+ "globalDependencies": [
79
+ {
80
+ "admin": ">=6.13.16"
81
+ }
82
+ ]
83
+ },
84
+ "native": {
85
+ "account": "",
86
+ "password": "",
87
+ "powerStationId": "",
88
+ "pollInterval": 300,
89
+ "requestTimeout": 15,
90
+ "maxConsecutiveErrors": 3,
91
+ "pushoverMode": "none",
92
+ "pushoverInstance": "pushover.0",
93
+ "pushoverUserKey": "",
94
+ "pushoverApiToken": "",
95
+ "pushoverPriority": 0,
96
+ "notifyOnLoginFailure": true,
97
+ "notifyOnRateLimit": true,
98
+ "notifyOnStationOffline": true,
99
+ "notifyOnAdapterError": true,
100
+ "stationOfflineMinutes": 30,
101
+ "debugRawResponse": false
102
+ },
103
+ "objects": [],
104
+ "instanceObjects": [
105
+ {
106
+ "_id": "info",
107
+ "type": "channel",
108
+ "common": {
109
+ "name": "Information"
110
+ },
111
+ "native": {}
112
+ },
113
+ {
114
+ "_id": "info.connection",
115
+ "type": "state",
116
+ "common": {
117
+ "role": "indicator.connected",
118
+ "name": "SEMS Portal reachable",
119
+ "type": "boolean",
120
+ "read": true,
121
+ "write": false,
122
+ "def": false
123
+ },
124
+ "native": {}
125
+ },
126
+ {
127
+ "_id": "info.lastSuccess",
128
+ "type": "state",
129
+ "common": {
130
+ "role": "value.time",
131
+ "name": "Timestamp of last successful poll",
132
+ "type": "number",
133
+ "read": true,
134
+ "write": false,
135
+ "def": 0
136
+ },
137
+ "native": {}
138
+ },
139
+ {
140
+ "_id": "info.lastError",
141
+ "type": "state",
142
+ "common": {
143
+ "role": "text",
144
+ "name": "Last error message",
145
+ "type": "string",
146
+ "read": true,
147
+ "write": false,
148
+ "def": ""
149
+ },
150
+ "native": {}
151
+ },
152
+ {
153
+ "_id": "info.consecutiveErrors",
154
+ "type": "state",
155
+ "common": {
156
+ "role": "value",
157
+ "name": "Number of consecutive failed polls",
158
+ "type": "number",
159
+ "read": true,
160
+ "write": false,
161
+ "def": 0
162
+ },
163
+ "native": {}
164
+ },
165
+ {
166
+ "_id": "info.rateLimited",
167
+ "type": "state",
168
+ "common": {
169
+ "role": "indicator",
170
+ "name": "SEMS Portal is currently rate-limiting this account",
171
+ "type": "boolean",
172
+ "read": true,
173
+ "write": false,
174
+ "def": false
175
+ },
176
+ "native": {}
177
+ },
178
+ {
179
+ "_id": "info.activePollInterval",
180
+ "type": "state",
181
+ "common": {
182
+ "role": "value.interval",
183
+ "name": "Currently active poll interval (seconds, incl. backoff)",
184
+ "type": "number",
185
+ "unit": "s",
186
+ "read": true,
187
+ "write": false,
188
+ "def": 300
189
+ },
190
+ "native": {}
191
+ },
192
+ {
193
+ "_id": "info.rawResponse",
194
+ "type": "state",
195
+ "common": {
196
+ "role": "json",
197
+ "name": "Raw last SEMS API response (debug, disabled by default)",
198
+ "type": "string",
199
+ "read": true,
200
+ "write": false,
201
+ "def": "",
202
+ "expert": true
203
+ },
204
+ "native": {}
205
+ }
206
+ ]
207
+ }
package/lib/errors.js ADDED
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * Base class for all errors raised by the SEMS API client.
5
+ * Keeping a dedicated hierarchy lets main.js react differently
6
+ * (log level, Pushover severity, retry behaviour) per error type
7
+ * instead of parsing error message strings.
8
+ */
9
+ class SemsError extends Error {
10
+ constructor(message) {
11
+ super(message);
12
+ this.name = this.constructor.name;
13
+ }
14
+ }
15
+
16
+ /** Login failed (wrong credentials, portal changed its login contract, ...). */
17
+ class SemsAuthError extends SemsError {}
18
+
19
+ /** The portal answered with its rate-limit code (observed: GY0429). */
20
+ class SemsRateLimitError extends SemsError {
21
+ /**
22
+ * @param {string} message
23
+ * @param {number} retryAfterSeconds recommended cool-down before the next request
24
+ */
25
+ constructor(message, retryAfterSeconds = 300) {
26
+ super(message);
27
+ this.retryAfterSeconds = retryAfterSeconds;
28
+ }
29
+ }
30
+
31
+ /** Network-level failure (timeout, DNS, connection reset, non-2xx, ...). */
32
+ class SemsNetworkError extends SemsError {}
33
+
34
+ /** The portal answered but the payload did not look like what we expected. */
35
+ class SemsProtocolError extends SemsError {}
36
+
37
+ module.exports = {
38
+ SemsError,
39
+ SemsAuthError,
40
+ SemsRateLimitError,
41
+ SemsNetworkError,
42
+ SemsProtocolError,
43
+ };