iobroker.zwavews 0.0.3
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/LICENSE +21 -0
- package/README.md +63 -0
- package/admin/i18n/de/translations.json +35 -0
- package/admin/i18n/en/translations.json +36 -0
- package/admin/i18n/es/translations.json +35 -0
- package/admin/i18n/fr/translations.json +35 -0
- package/admin/i18n/it/translations.json +35 -0
- package/admin/i18n/nl/translations.json +35 -0
- package/admin/i18n/pl/translations.json +36 -0
- package/admin/i18n/pt/translations.json +35 -0
- package/admin/i18n/ru/translations.json +35 -0
- package/admin/i18n/uk/translations.json +35 -0
- package/admin/i18n/zh-cn/translations.json +35 -0
- package/admin/jsonConfig.json +347 -0
- package/admin/zwavews.png +0 -0
- package/io-package.json +246 -0
- package/lib/adapter-config.d.ts +19 -0
- package/lib/constants.js +25 -0
- package/lib/helper.js +540 -0
- package/lib/messages.js +49 -0
- package/lib/mqttServerController.js +78 -0
- package/lib/statesController.js +84 -0
- package/lib/utils.js +205 -0
- package/lib/websocketController.js +131 -0
- package/main.js +407 -0
- package/package.json +75 -0
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
*/
|
|
4
|
+
class StatesController {
|
|
5
|
+
/**
|
|
6
|
+
*
|
|
7
|
+
* @param adapter
|
|
8
|
+
* @param deviceCache
|
|
9
|
+
*/
|
|
10
|
+
constructor(adapter) {
|
|
11
|
+
this.adapter = adapter;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
*
|
|
16
|
+
* @param stateName
|
|
17
|
+
* @param value
|
|
18
|
+
*/
|
|
19
|
+
async setStateSafelyAsync(stateName, value) {
|
|
20
|
+
if (value === undefined || value === null) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
await this.adapter.setStateAsync(stateName, value, true);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
*
|
|
28
|
+
* @param stateName
|
|
29
|
+
* @param value
|
|
30
|
+
*/
|
|
31
|
+
async setStateChangedSafelyAsync(stateName, value) {
|
|
32
|
+
if (value === undefined || value === null) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
await this.adapter.setStateChangedAsync(stateName, value, true);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
*
|
|
40
|
+
* @param deviceCache
|
|
41
|
+
*/
|
|
42
|
+
async subscribeAllWritableExistsStates() {
|
|
43
|
+
const writableStates = {};
|
|
44
|
+
|
|
45
|
+
const res = await this.adapter.getObjectViewAsync("system", "state", {
|
|
46
|
+
startkey: "zwaveWS.",
|
|
47
|
+
endkey: "zwaveWS.\u9999",
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
for (const row of res.rows) {
|
|
51
|
+
const obj = row.value;
|
|
52
|
+
if (obj?.common?.write === true) {
|
|
53
|
+
writableStates[obj._id] = {
|
|
54
|
+
mqttId: obj.native.mqttPath,
|
|
55
|
+
write: true,
|
|
56
|
+
subst: null,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return writableStates;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
*
|
|
66
|
+
*/
|
|
67
|
+
async setAllAvailableToFalse() {
|
|
68
|
+
const readyStates = await this.adapter.getStatesAsync("*.ready");
|
|
69
|
+
for (const readyState in readyStates) {
|
|
70
|
+
await this.adapter.setStateChangedAsync(readyState, false, true);
|
|
71
|
+
}
|
|
72
|
+
const availableStates = await this.adapter.getStatesAsync("*.status");
|
|
73
|
+
for (const availableState in availableStates) {
|
|
74
|
+
await this.adapter.setStateChangedAsync(availableState, "unknown", true);
|
|
75
|
+
}
|
|
76
|
+
await this.adapter.setStateChangedAsync('info.zwave_gateway_status', 'offline', true);
|
|
77
|
+
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
module.exports = {
|
|
83
|
+
StatesController,
|
|
84
|
+
};
|
package/lib/utils.js
ADDED
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* @param ba
|
|
4
|
+
*/
|
|
5
|
+
function bytesArrayToWordArray(ba) {
|
|
6
|
+
const wa = [];
|
|
7
|
+
for (let i = 0; i < ba.length; i++) {
|
|
8
|
+
wa[(i / 2) | 0] |= ba[i] << (8 * (i % 2));
|
|
9
|
+
}
|
|
10
|
+
return wa;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// If the value is greater than 1000, kelvin is assumed.
|
|
14
|
+
// If smaller, it is assumed to be mired.
|
|
15
|
+
/**
|
|
16
|
+
*
|
|
17
|
+
* @param t
|
|
18
|
+
*/
|
|
19
|
+
function toMired(t) {
|
|
20
|
+
let miredValue = t;
|
|
21
|
+
if (t > 1000) {
|
|
22
|
+
miredValue = miredKelvinConversion(t);
|
|
23
|
+
}
|
|
24
|
+
return miredValue;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
*
|
|
29
|
+
* @param t
|
|
30
|
+
*/
|
|
31
|
+
function miredKelvinConversion(t) {
|
|
32
|
+
return Math.round(1000000 / t);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Converts a decimal number to a hex string with zero-padding
|
|
37
|
+
*
|
|
38
|
+
* @param decimal
|
|
39
|
+
* @param padding
|
|
40
|
+
*/
|
|
41
|
+
function decimalToHex(decimal, padding) {
|
|
42
|
+
let hex = Number(decimal).toString(16);
|
|
43
|
+
padding =
|
|
44
|
+
typeof padding === "undefined" || padding === null
|
|
45
|
+
? (padding = 2)
|
|
46
|
+
: padding;
|
|
47
|
+
|
|
48
|
+
while (hex.length < padding) {
|
|
49
|
+
hex = `0${hex}`;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return hex;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
*
|
|
57
|
+
* @param array
|
|
58
|
+
*/
|
|
59
|
+
function clearArray(array) {
|
|
60
|
+
while (array.length > 0) {
|
|
61
|
+
array.pop();
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
*
|
|
67
|
+
* @param source
|
|
68
|
+
* @param target
|
|
69
|
+
*/
|
|
70
|
+
function moveArray(source, target) {
|
|
71
|
+
while (source.length > 0) {
|
|
72
|
+
target.push(source.shift());
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
*
|
|
78
|
+
* @param item
|
|
79
|
+
*/
|
|
80
|
+
function isObject(item) {
|
|
81
|
+
return typeof item === "object" && !Array.isArray(item) && item !== null;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
*
|
|
86
|
+
* @param item
|
|
87
|
+
*/
|
|
88
|
+
function isJson(item) {
|
|
89
|
+
let value = typeof item !== "string" ? JSON.stringify(item) : item;
|
|
90
|
+
try {
|
|
91
|
+
value = JSON.parse(value);
|
|
92
|
+
} catch (e) {
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return typeof value === "object" && value !== null;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
*
|
|
101
|
+
* @param input
|
|
102
|
+
*/
|
|
103
|
+
function getLastSegment(input) {
|
|
104
|
+
if (typeof input !== "string") {
|
|
105
|
+
return "";
|
|
106
|
+
}
|
|
107
|
+
const parts = input.split(/[./]/).filter(Boolean);
|
|
108
|
+
return parts.length ? parts[parts.length - 1] : "";
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* @param {any} value
|
|
113
|
+
* @returns {boolean}
|
|
114
|
+
*/
|
|
115
|
+
function isNumeric(value) {
|
|
116
|
+
if (value === null || value === undefined) {
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
if (typeof value === "number") {
|
|
120
|
+
return Number.isFinite(value);
|
|
121
|
+
}
|
|
122
|
+
if (typeof value === "string") {
|
|
123
|
+
const s = value.trim();
|
|
124
|
+
if (s === "") {
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
return /^[-+]?(?:\d+\.?\d*|\.\d+)(?:[eE][-+]?\d+)?$/.test(s);
|
|
128
|
+
}
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
*
|
|
134
|
+
* @param str
|
|
135
|
+
*/
|
|
136
|
+
function replaceLastDot(str) {
|
|
137
|
+
const idx = str.lastIndexOf(".");
|
|
138
|
+
return idx >= 0 ? `${str.slice(0, idx)}_${str.slice(idx + 1)}` : str;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
*
|
|
142
|
+
* @param input
|
|
143
|
+
*/
|
|
144
|
+
function formatMQTT(input) {
|
|
145
|
+
if (typeof input !== "string") {
|
|
146
|
+
return "";
|
|
147
|
+
}
|
|
148
|
+
return input.replace(/\./g, "/");
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
*
|
|
153
|
+
* @param nodeId
|
|
154
|
+
* @param width
|
|
155
|
+
*/
|
|
156
|
+
function padNodeId(nodeId, width = 3) {
|
|
157
|
+
return nodeId.replace(/(\d+)$/, (m) => m.padStart(width, "0"));
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
*
|
|
162
|
+
* @param status
|
|
163
|
+
*/
|
|
164
|
+
function getStatusText(status) {
|
|
165
|
+
const nodeStatus = {
|
|
166
|
+
0: "Unknown",
|
|
167
|
+
1: "Asleep",
|
|
168
|
+
2: "Awake",
|
|
169
|
+
3: "Dead",
|
|
170
|
+
4: "Alive",
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
return nodeStatus[status] || "Unknown";
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
*
|
|
178
|
+
* @param nodeIdOriginal
|
|
179
|
+
*/
|
|
180
|
+
function formatNodeId(nodeIdOriginal) {
|
|
181
|
+
let nodeId = nodeIdOriginal;
|
|
182
|
+
|
|
183
|
+
if (this.isNumeric(nodeIdOriginal)) {
|
|
184
|
+
nodeId = this.padNodeId(`nodeID_${nodeIdOriginal}`);
|
|
185
|
+
}
|
|
186
|
+
return nodeId;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
module.exports = {
|
|
190
|
+
bytesArrayToWordArray,
|
|
191
|
+
toMired,
|
|
192
|
+
miredKelvinConversion,
|
|
193
|
+
decimalToHex,
|
|
194
|
+
formatNodeId,
|
|
195
|
+
clearArray,
|
|
196
|
+
moveArray,
|
|
197
|
+
isObject,
|
|
198
|
+
isJson,
|
|
199
|
+
getLastSegment,
|
|
200
|
+
isNumeric,
|
|
201
|
+
replaceLastDot,
|
|
202
|
+
formatMQTT,
|
|
203
|
+
padNodeId,
|
|
204
|
+
getStatusText,
|
|
205
|
+
};
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
const WebSocket = require('ws');
|
|
2
|
+
let wsClient;
|
|
3
|
+
const wsHeartbeatIntervall = 5000;
|
|
4
|
+
const restartTimeout = 1000;
|
|
5
|
+
let ping;
|
|
6
|
+
let pingTimeout;
|
|
7
|
+
let autoRestartTimeout;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
*
|
|
11
|
+
*/
|
|
12
|
+
class WebsocketController {
|
|
13
|
+
/**
|
|
14
|
+
*
|
|
15
|
+
* @param adapter
|
|
16
|
+
*/
|
|
17
|
+
constructor(adapter) {
|
|
18
|
+
this.adapter = adapter;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
*
|
|
23
|
+
*/
|
|
24
|
+
initWsClient() {
|
|
25
|
+
try {
|
|
26
|
+
let wsURL = `${this.adapter.config.wsScheme}://${this.adapter.config.wsServerIP}:${this.adapter.config.wsServerPort}/api`;
|
|
27
|
+
|
|
28
|
+
if (this.adapter.config.wsTokenEnabled == true) {
|
|
29
|
+
wsURL += `?token=${this.adapter.config.wsToken}`;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
wsClient = new WebSocket(wsURL, { rejectUnauthorized: false });
|
|
33
|
+
|
|
34
|
+
wsClient.on('open', () => {
|
|
35
|
+
// Send ping to server
|
|
36
|
+
this.sendPingToServer();
|
|
37
|
+
// Start Heartbeat
|
|
38
|
+
this.wsHeartbeat();
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
wsClient.on('pong', () => {
|
|
42
|
+
this.wsHeartbeat();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
wsClient.on('close', async () => {
|
|
46
|
+
clearTimeout(pingTimeout);
|
|
47
|
+
clearTimeout(ping);
|
|
48
|
+
|
|
49
|
+
if (wsClient.readyState === WebSocket.CLOSED) {
|
|
50
|
+
this.autoRestart();
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
wsClient.on('message', () => {});
|
|
55
|
+
|
|
56
|
+
wsClient.on('error', (err) => {
|
|
57
|
+
this.adapter.log.debug(err);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
return wsClient;
|
|
61
|
+
} catch (err) {
|
|
62
|
+
this.adapter.log.error(err);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
*
|
|
68
|
+
* @param message
|
|
69
|
+
*/
|
|
70
|
+
send(message) {
|
|
71
|
+
if (wsClient.readyState !== WebSocket.OPEN) {
|
|
72
|
+
this.adapter.log.warn('Cannot set State, no websocket connection to Zigbee2MQTT!');
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
wsClient.send(message);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
*
|
|
80
|
+
*/
|
|
81
|
+
sendPingToServer() {
|
|
82
|
+
//this.logDebug('Send ping to server');
|
|
83
|
+
wsClient.ping();
|
|
84
|
+
ping = setTimeout(() => {
|
|
85
|
+
this.sendPingToServer();
|
|
86
|
+
}, wsHeartbeatIntervall);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
*
|
|
91
|
+
*/
|
|
92
|
+
wsHeartbeat() {
|
|
93
|
+
clearTimeout(pingTimeout);
|
|
94
|
+
pingTimeout = setTimeout(() => {
|
|
95
|
+
this.adapter.log.warn('Websocked connection timed out');
|
|
96
|
+
wsClient.terminate();
|
|
97
|
+
}, wsHeartbeatIntervall + 3000);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
*
|
|
102
|
+
*/
|
|
103
|
+
async autoRestart() {
|
|
104
|
+
this.adapter.log.warn(`Start try again in ${restartTimeout / 1000} seconds...`);
|
|
105
|
+
autoRestartTimeout = setTimeout(() => {
|
|
106
|
+
this.adapter.startWebsocket();
|
|
107
|
+
}, restartTimeout);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
*
|
|
112
|
+
*/
|
|
113
|
+
closeConnection() {
|
|
114
|
+
if (wsClient && wsClient.readyState !== WebSocket.CLOSED) {
|
|
115
|
+
wsClient.close();
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
*
|
|
121
|
+
*/
|
|
122
|
+
async allTimerClear() {
|
|
123
|
+
clearTimeout(pingTimeout);
|
|
124
|
+
clearTimeout(ping);
|
|
125
|
+
clearTimeout(autoRestartTimeout);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
module.exports = {
|
|
130
|
+
WebsocketController,
|
|
131
|
+
};
|