iobroker.easee 2.0.0-alpha.0 → 2.0.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 +8 -4
- package/admin/i18n/de/translations.json +11 -0
- package/admin/i18n/en/translations.json +11 -0
- package/admin/i18n/es/translations.json +11 -0
- package/admin/i18n/fr/translations.json +11 -0
- package/admin/i18n/it/translations.json +11 -0
- package/admin/i18n/nl/translations.json +11 -0
- package/admin/i18n/pl/translations.json +11 -0
- package/admin/i18n/pt/translations.json +11 -0
- package/admin/i18n/ru/translations.json +11 -0
- package/admin/i18n/uk/translations.json +11 -0
- package/admin/i18n/zh-cn/translations.json +11 -0
- package/admin/jsonConfig.json +73 -0
- package/admin/words.js +23 -12
- package/io-package.json +67 -1
- package/lib/enum.js +4 -4
- package/lib/observations.js +89 -0
- package/lib/signalr-http-client.js +83 -0
- package/lib/tools.js +44 -32
- package/main.js +2269 -1144
- package/package.json +4 -3
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const CHARGER_STATE_OBSERVATION_IDS = [
|
|
4
|
+
46, 48, 68, 80, 96, 102, 103, 109, 111, 112, 113, 114, 120, 122, 124, 132,
|
|
5
|
+
182, 183, 184, 185, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 250,
|
|
6
|
+
];
|
|
7
|
+
|
|
8
|
+
const STATE_FIELDS_BY_OBSERVATION_ID = {
|
|
9
|
+
46: "ledMode",
|
|
10
|
+
48: "dynamicChargerCurrent",
|
|
11
|
+
68: "wiFiAPEnabled",
|
|
12
|
+
80: "chargerFirmware",
|
|
13
|
+
96: "reasonForNoCurrent",
|
|
14
|
+
102: "smartCharging",
|
|
15
|
+
103: "cableLocked",
|
|
16
|
+
109: "chargerOpMode",
|
|
17
|
+
111: "dynamicCircuitCurrentP1",
|
|
18
|
+
112: "dynamicCircuitCurrentP2",
|
|
19
|
+
113: "dynamicCircuitCurrentP3",
|
|
20
|
+
114: "outputCurrent",
|
|
21
|
+
120: "totalPower",
|
|
22
|
+
122: "energyPerHour",
|
|
23
|
+
124: "lifetimeEnergy",
|
|
24
|
+
132: "wiFiRSSI",
|
|
25
|
+
182: "inCurrentT2",
|
|
26
|
+
183: "inCurrentT3",
|
|
27
|
+
184: "inCurrentT4",
|
|
28
|
+
185: "inCurrentT5",
|
|
29
|
+
190: "inVoltageT1T2",
|
|
30
|
+
191: "inVoltageT1T3",
|
|
31
|
+
192: "inVoltageT1T4",
|
|
32
|
+
193: "inVoltageT1T5",
|
|
33
|
+
194: "inVoltageT2T3",
|
|
34
|
+
195: "inVoltageT2T4",
|
|
35
|
+
196: "inVoltageT2T5",
|
|
36
|
+
197: "inVoltageT3T4",
|
|
37
|
+
198: "inVoltageT3T5",
|
|
38
|
+
199: "inVoltageT4T5",
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const SUPPLY_VOLTAGE_FIELDS = [
|
|
42
|
+
"inVoltageT2T3",
|
|
43
|
+
"inVoltageT1T3",
|
|
44
|
+
"inVoltageT1T4",
|
|
45
|
+
"inVoltageT1T5",
|
|
46
|
+
"inVoltageT2T4",
|
|
47
|
+
"inVoltageT2T5",
|
|
48
|
+
];
|
|
49
|
+
|
|
50
|
+
function observationsToChargerState(responseData) {
|
|
51
|
+
const observations = Array.isArray(responseData?.observations)
|
|
52
|
+
? responseData.observations
|
|
53
|
+
: [];
|
|
54
|
+
/** @type {Record<string, any>} */
|
|
55
|
+
const chargerState = Object.fromEntries(
|
|
56
|
+
Object.values(STATE_FIELDS_BY_OBSERVATION_ID).map((field) => [field, null]),
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
for (const observation of observations) {
|
|
60
|
+
const field = STATE_FIELDS_BY_OBSERVATION_ID[observation.id];
|
|
61
|
+
if (field !== undefined) {
|
|
62
|
+
chargerState[field] = observation.value;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const cloudObservation = observations.find(
|
|
67
|
+
(observation) => observation.id === 250,
|
|
68
|
+
);
|
|
69
|
+
if (cloudObservation) {
|
|
70
|
+
chargerState.isOnline = Boolean(cloudObservation.value);
|
|
71
|
+
}
|
|
72
|
+
chargerState.voltage = null;
|
|
73
|
+
for (const field of SUPPLY_VOLTAGE_FIELDS) {
|
|
74
|
+
const value = chargerState[field];
|
|
75
|
+
if (
|
|
76
|
+
typeof value === "number" &&
|
|
77
|
+
Number.isFinite(value) &&
|
|
78
|
+
value >= 100 &&
|
|
79
|
+
value <= 300
|
|
80
|
+
) {
|
|
81
|
+
chargerState.voltage = value;
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return chargerState;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
module.exports = { CHARGER_STATE_OBSERVATION_IDS, observationsToChargerState };
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const axios = require("axios").default;
|
|
4
|
+
const signalR = require("@microsoft/signalr");
|
|
5
|
+
const { CookieJar } = require("tough-cookie");
|
|
6
|
+
|
|
7
|
+
function hasHeader(headers, name) {
|
|
8
|
+
return Object.keys(headers).some(
|
|
9
|
+
(headerName) => headerName.toLowerCase() === name.toLowerCase(),
|
|
10
|
+
);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
class AxiosSignalRHttpClient extends signalR.HttpClient {
|
|
14
|
+
/** @param {any} [httpClient] */
|
|
15
|
+
constructor(httpClient = axios) {
|
|
16
|
+
super();
|
|
17
|
+
this.httpClient = httpClient;
|
|
18
|
+
this.cookieJar = new CookieJar();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async send(request) {
|
|
22
|
+
if (!request.url) {
|
|
23
|
+
throw new Error("SignalR HTTP request URL is missing");
|
|
24
|
+
}
|
|
25
|
+
const headers = { ...request.headers };
|
|
26
|
+
const cookie = this.cookieJar.getCookieStringSync(request.url);
|
|
27
|
+
if (cookie && !hasHeader(headers, "cookie")) {
|
|
28
|
+
headers.Cookie = cookie;
|
|
29
|
+
}
|
|
30
|
+
const cancelSource = axios.CancelToken.source();
|
|
31
|
+
const abortHandler = () =>
|
|
32
|
+
cancelSource.cancel("SignalR HTTP request aborted");
|
|
33
|
+
if (request.abortSignal) {
|
|
34
|
+
request.abortSignal.onabort = abortHandler;
|
|
35
|
+
}
|
|
36
|
+
try {
|
|
37
|
+
const response = await this.httpClient.request({
|
|
38
|
+
method: request.method || "GET",
|
|
39
|
+
url: request.url,
|
|
40
|
+
headers,
|
|
41
|
+
data: request.content,
|
|
42
|
+
timeout: request.timeout,
|
|
43
|
+
cancelToken: cancelSource.token,
|
|
44
|
+
responseType:
|
|
45
|
+
request.responseType === "arraybuffer" ? "arraybuffer" : "text",
|
|
46
|
+
transformResponse: [(data) => data],
|
|
47
|
+
validateStatus: () => true,
|
|
48
|
+
});
|
|
49
|
+
for (const setCookie of response.headers?.["set-cookie"] || []) {
|
|
50
|
+
await this.cookieJar.setCookie(setCookie, request.url, {
|
|
51
|
+
ignoreError: true,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
let content = response.data;
|
|
55
|
+
if (Buffer.isBuffer(content) && request.responseType === "arraybuffer") {
|
|
56
|
+
content = content.buffer.slice(
|
|
57
|
+
content.byteOffset,
|
|
58
|
+
content.byteOffset + content.byteLength,
|
|
59
|
+
);
|
|
60
|
+
} else if (
|
|
61
|
+
typeof content !== "string" &&
|
|
62
|
+
request.responseType !== "arraybuffer"
|
|
63
|
+
) {
|
|
64
|
+
content = content == null ? "" : JSON.stringify(content);
|
|
65
|
+
}
|
|
66
|
+
return new signalR.HttpResponse(
|
|
67
|
+
response.status,
|
|
68
|
+
response.statusText,
|
|
69
|
+
content,
|
|
70
|
+
);
|
|
71
|
+
} finally {
|
|
72
|
+
if (request.abortSignal?.onabort === abortHandler) {
|
|
73
|
+
request.abortSignal.onabort = null;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
getCookieString(url) {
|
|
79
|
+
return this.cookieJar.getCookieStringSync(url);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
module.exports = AxiosSignalRHttpClient;
|
package/lib/tools.js
CHANGED
|
@@ -2,84 +2,96 @@ const axios = require("axios").default;
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Tests whether the given variable is a real object and not an Array
|
|
5
|
+
*
|
|
5
6
|
* @param {any} it The variable to test
|
|
6
7
|
* @returns {it is Record<string, any>} True if the variable is an object, false otherwise
|
|
7
8
|
*/
|
|
8
9
|
function isObject(it) {
|
|
9
|
-
|
|
10
|
+
return Object.prototype.toString.call(it) === "[object Object]";
|
|
10
11
|
}
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
14
|
* Tests whether the given variable is really an Array
|
|
15
|
+
*
|
|
14
16
|
* @param {any} it The variable to test
|
|
15
17
|
* @returns {it is any[]} True if the variable is an array, false otherwise
|
|
16
18
|
*/
|
|
17
19
|
function isArray(it) {
|
|
18
|
-
|
|
20
|
+
return Array.isArray(it);
|
|
19
21
|
}
|
|
20
22
|
|
|
21
23
|
/**
|
|
22
24
|
* Translates text to the target language. Automatically chooses the right translation API.
|
|
25
|
+
*
|
|
23
26
|
* @param {string} text The text to translate
|
|
24
27
|
* @param {string} targetLang The target language
|
|
25
28
|
* @param {string} [yandexApiKey] The yandex API key. You can create one for free at https://translate.yandex.com/developers
|
|
26
29
|
* @returns {Promise<string>} The translated text
|
|
27
30
|
*/
|
|
28
31
|
async function translateText(text, targetLang, yandexApiKey) {
|
|
29
|
-
|
|
30
|
-
|
|
32
|
+
if (!text) {
|
|
33
|
+
return "";
|
|
34
|
+
}
|
|
35
|
+
if (targetLang === "en") {
|
|
36
|
+
return text;
|
|
37
|
+
}
|
|
31
38
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
39
|
+
return yandexApiKey
|
|
40
|
+
? translateYandex(text, targetLang, yandexApiKey)
|
|
41
|
+
: translateGoogle(text, targetLang);
|
|
35
42
|
}
|
|
36
43
|
|
|
37
44
|
/**
|
|
38
45
|
* Translates text with Yandex API
|
|
46
|
+
*
|
|
39
47
|
* @param {string} text The text to translate
|
|
40
48
|
* @param {string} targetLang The target language
|
|
41
49
|
* @param {string} apiKey The yandex API key. You can create one for free at https://translate.yandex.com/developers
|
|
42
50
|
* @returns {Promise<string>} The translated text
|
|
43
51
|
*/
|
|
44
52
|
async function translateYandex(text, targetLang, apiKey) {
|
|
45
|
-
|
|
53
|
+
if (targetLang === "zh-cn") {
|
|
54
|
+
targetLang = "zh";
|
|
55
|
+
}
|
|
46
56
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
}
|
|
53
|
-
throw new Error('Invalid response for translate request');
|
|
54
|
-
} catch (e) {
|
|
55
|
-
throw new Error(`Could not translate to "${targetLang}": ${e.message}`);
|
|
57
|
+
const url = `https://translate.yandex.net/api/v1.5/tr.json/translate?key=${apiKey}&text=${encodeURIComponent(text)}&lang=en-${targetLang}`;
|
|
58
|
+
try {
|
|
59
|
+
const response = await axios.get(url, { timeout: 15000 });
|
|
60
|
+
if (response.data?.text && isArray(response.data.text)) {
|
|
61
|
+
return response.data.text[0];
|
|
56
62
|
}
|
|
63
|
+
throw new Error("Invalid response for translate request");
|
|
64
|
+
} catch (e) {
|
|
65
|
+
throw new Error(`Could not translate to "${targetLang}": ${e.message}`);
|
|
66
|
+
}
|
|
57
67
|
}
|
|
58
68
|
|
|
59
69
|
/**
|
|
60
70
|
* Translates text with Google API
|
|
71
|
+
*
|
|
61
72
|
* @param {string} text The text to translate
|
|
62
73
|
* @param {string} targetLang The target language
|
|
63
74
|
* @returns {Promise<string>} The translated text
|
|
64
75
|
*/
|
|
65
76
|
async function translateGoogle(text, targetLang) {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
}
|
|
72
|
-
throw new Error('Invalid response for translate request');
|
|
73
|
-
} catch (e) {
|
|
74
|
-
const errorMessage = e.response?.status === 429
|
|
75
|
-
? `Rate-limited by Google Translate`
|
|
76
|
-
: e.message;
|
|
77
|
-
throw new Error(`Could not translate to "${targetLang}": ${errorMessage}`);
|
|
77
|
+
const url = `http://translate.googleapis.com/translate_a/single?client=gtx&sl=en&tl=${targetLang}&dt=t&q=${encodeURIComponent(text)}&ie=UTF-8&oe=UTF-8`;
|
|
78
|
+
try {
|
|
79
|
+
const response = await axios.get(url, { timeout: 15000 });
|
|
80
|
+
if (isArray(response.data)) {
|
|
81
|
+
return response.data[0][0][0];
|
|
78
82
|
}
|
|
83
|
+
throw new Error("Invalid response for translate request");
|
|
84
|
+
} catch (e) {
|
|
85
|
+
const errorMessage =
|
|
86
|
+
e.response?.status === 429
|
|
87
|
+
? `Rate-limited by Google Translate`
|
|
88
|
+
: e.message;
|
|
89
|
+
throw new Error(`Could not translate to "${targetLang}": ${errorMessage}`);
|
|
90
|
+
}
|
|
79
91
|
}
|
|
80
92
|
|
|
81
93
|
module.exports = {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
94
|
+
isArray,
|
|
95
|
+
isObject,
|
|
96
|
+
translateText,
|
|
85
97
|
};
|