zwave-js-ui 9.3.2
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 +48 -0
- package/dev-dist/registerSW.js +5 -0
- package/dist/apple-touch-icon-180x180.png +0 -0
- package/dist/assets/AssociationGroups-474358b5.js +1 -0
- package/dist/assets/BgRssiChart-3e7d7555.css +1 -0
- package/dist/assets/BgRssiChart-554c89ef.js +3 -0
- package/dist/assets/BlinkIcon-5407e2e0.js +1 -0
- package/dist/assets/ColumnFilter-378ec338.js +1 -0
- package/dist/assets/ColumnFilterBoolean-e0ff8ce6.js +1 -0
- package/dist/assets/ColumnFilterDate-b17871ea.js +1 -0
- package/dist/assets/ColumnFilterNumber-c07930a6.js +1 -0
- package/dist/assets/ColumnFilterString-079ac562.js +1 -0
- package/dist/assets/ControlPanel-3c3168d8.js +7 -0
- package/dist/assets/ControllerChart-f2fbb771.js +1 -0
- package/dist/assets/Debug-ea34e06e.js +98 -0
- package/dist/assets/DialogAdvanced-5f8c36c7.js +1 -0
- package/dist/assets/DialogAdvanced-eaabda04.css +1 -0
- package/dist/assets/DialogAssociation-6da4cd66.js +1 -0
- package/dist/assets/DialogGatewayValue-501b7bbe.js +1 -0
- package/dist/assets/DialogGatewayValue-911dd212.css +1 -0
- package/dist/assets/DialogHealthCheck-2228ed6c.css +1 -0
- package/dist/assets/DialogHealthCheck-9e90523a.js +1 -0
- package/dist/assets/DialogSceneValue-ff3081b9.js +1 -0
- package/dist/assets/ErrorPage-a4f837c6.js +1 -0
- package/dist/assets/ExpandedNode-9f727a42.css +1 -0
- package/dist/assets/ExpandedNode-a0bff765.js +3 -0
- package/dist/assets/HomeAssistant-974143e2.js +1 -0
- package/dist/assets/ListInput-d8c678f2.js +1 -0
- package/dist/assets/Login-1826b0a1.css +1 -0
- package/dist/assets/Login-5424b4df.js +1 -0
- package/dist/assets/MaterialIcons-Regular-11ec382a.woff +0 -0
- package/dist/assets/MaterialIcons-Regular-29c11fa5.ttf +0 -0
- package/dist/assets/MaterialIcons-Regular-5743ed3d.woff2 +0 -0
- package/dist/assets/MaterialIcons-Regular-e69d687a.eot +0 -0
- package/dist/assets/Mesh-1b8d31eb.css +1 -0
- package/dist/assets/Mesh-25b19dfe.js +1 -0
- package/dist/assets/NodeDetails-da00c417.css +1 -0
- package/dist/assets/NodeDetails-e9d8d566.js +5 -0
- package/dist/assets/NodePanel-3639f227.css +1 -0
- package/dist/assets/NodePanel-5cdb1b4a.js +1 -0
- package/dist/assets/NodeScheduler-e91e411b.js +1 -0
- package/dist/assets/OTAUpdates-066cbfed.js +7 -0
- package/dist/assets/QrReader-00dee3fb.css +1 -0
- package/dist/assets/QrReader-c320584c.js +1 -0
- package/dist/assets/ReinterviewBadge-a40fb19f.js +1 -0
- package/dist/assets/RichValue-3ea76a33.css +1 -0
- package/dist/assets/RichValue-3f78fa98.js +1 -0
- package/dist/assets/Scenes-4aea74b8.js +1 -0
- package/dist/assets/Settings-ac079344.css +1 -0
- package/dist/assets/Settings-d8e203ed.js +44 -0
- package/dist/assets/SmartStart-63c21be6.js +2 -0
- package/dist/assets/SmartView-f9259d20.js +1 -0
- package/dist/assets/StatisticsArrows-40c56587.js +1 -0
- package/dist/assets/StatisticsCard-a91596c0.js +1 -0
- package/dist/assets/Store-701d51c8.js +1 -0
- package/dist/assets/Store-a58f7130.css +1 -0
- package/dist/assets/UserCodeTable-c397703b.js +1 -0
- package/dist/assets/ValueId-b8e1e4a2.js +1 -0
- package/dist/assets/ValueId-ea679e64.css +1 -0
- package/dist/assets/ZwaveGraph-4fa851e3.js +98 -0
- package/dist/assets/ZwaveGraph-fe07d4ee.css +1 -0
- package/dist/assets/file-input-064e7979.js +1 -0
- package/dist/assets/index-058391ec.js +17 -0
- package/dist/assets/index-3c63af0b.js +112 -0
- package/dist/assets/index-468a52af.js +2 -0
- package/dist/assets/index-be785cef.css +9 -0
- package/dist/assets/index-d98f5afd.css +1 -0
- package/dist/assets/items-45b4c1f5.js +1 -0
- package/dist/assets/mdi-4fe99e39.js +1 -0
- package/dist/assets/prismeditor.esm-3002a813.js +7 -0
- package/dist/assets/qr-scanner-worker.min-5f44a019.js +98 -0
- package/dist/assets/vuedraggable.umd-441070b9.js +8 -0
- package/dist/favicon.ico +0 -0
- package/dist/index.html +47 -0
- package/dist/logo.png +0 -0
- package/dist/logo.svg +32 -0
- package/dist/manifest.webmanifest +1 -0
- package/dist/maskable-icon-512x512.png +0 -0
- package/dist/pwa-192x192.png +0 -0
- package/dist/pwa-512x512.png +0 -0
- package/dist/pwa-64x64.png +0 -0
- package/dist/registerSW.js +1 -0
- package/dist/star.svg +3 -0
- package/dist/sw.js +1232 -0
- package/package.json +217 -0
- package/public/apple-touch-icon-180x180.png +0 -0
- package/public/favicon.ico +0 -0
- package/public/logo.png +0 -0
- package/public/logo.svg +32 -0
- package/public/maskable-icon-512x512.png +0 -0
- package/public/pwa-192x192.png +0 -0
- package/public/pwa-512x512.png +0 -0
- package/public/pwa-64x64.png +0 -0
- package/public/star.svg +3 -0
- package/server/app.js +1201 -0
- package/server/bin/www.js +70 -0
- package/server/config/app.js +22 -0
- package/server/config/store.js +19 -0
- package/server/hass/configurations.js +195 -0
- package/server/hass/devices.js +397 -0
- package/server/lib/BackupManager.js +134 -0
- package/server/lib/Constants.js +705 -0
- package/server/lib/CustomPlugin.js +9 -0
- package/server/lib/EventEmitter.js +14 -0
- package/server/lib/Gateway.js +1986 -0
- package/server/lib/MqttClient.js +418 -0
- package/server/lib/SocketEvents.js +34 -0
- package/server/lib/SocketManager.js +71 -0
- package/server/lib/ZwaveClient.js +4261 -0
- package/server/lib/jsonStore.js +139 -0
- package/server/lib/logger.js +169 -0
- package/server/lib/utils.js +283 -0
- package/snippets/access-store-dir.js +12 -0
- package/snippets/clone-config.js +17 -0
- package/snippets/pingNodes.js +14 -0
- package/snippets/reinterview-nodes.js +64 -0
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const mqtt_1 = require("mqtt");
|
|
4
|
+
const utils_1 = require("./utils");
|
|
5
|
+
const logger_1 = require("./logger");
|
|
6
|
+
const EventEmitter_1 = require("./EventEmitter");
|
|
7
|
+
const app_1 = require("../config/app");
|
|
8
|
+
const fs_extra_1 = require("fs-extra");
|
|
9
|
+
const mqtt_jsonl_store_1 = require("mqtt-jsonl-store");
|
|
10
|
+
const path_1 = require("path");
|
|
11
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
12
|
+
const url = require('native-url');
|
|
13
|
+
const logger = (0, logger_1.module)('Mqtt');
|
|
14
|
+
class MqttClient extends EventEmitter_1.TypedEventEmitter {
|
|
15
|
+
config;
|
|
16
|
+
toSubscribe;
|
|
17
|
+
_clientID;
|
|
18
|
+
client;
|
|
19
|
+
error;
|
|
20
|
+
closed;
|
|
21
|
+
retrySubTimeout;
|
|
22
|
+
_closeTimeout;
|
|
23
|
+
storeManager;
|
|
24
|
+
static CLIENTS_PREFIX = '_CLIENTS';
|
|
25
|
+
static get EVENTS_PREFIX() {
|
|
26
|
+
return '_EVENTS';
|
|
27
|
+
}
|
|
28
|
+
static NAME_PREFIX = 'ZWAVE_GATEWAY-';
|
|
29
|
+
static ACTIONS = ['broadcast', 'api', 'multicast'];
|
|
30
|
+
static HASS_WILL = 'homeassistant/status';
|
|
31
|
+
static STATUS_TOPIC = 'status';
|
|
32
|
+
static VERSION_TOPIC = 'version';
|
|
33
|
+
get clientID() {
|
|
34
|
+
return this._clientID;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* The constructor
|
|
38
|
+
*/
|
|
39
|
+
constructor(config) {
|
|
40
|
+
super();
|
|
41
|
+
this._init(config).catch((e) => {
|
|
42
|
+
logger.error('Error while initializing MQTT Client', e);
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
get connected() {
|
|
46
|
+
return this.client && this.client.connected;
|
|
47
|
+
}
|
|
48
|
+
get disabled() {
|
|
49
|
+
return this.config.disabled;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Returns the topic used to send client and devices status updateStates
|
|
53
|
+
* if name is null the client is the gateway itself
|
|
54
|
+
*/
|
|
55
|
+
getClientTopic(suffix) {
|
|
56
|
+
return `${this.config.prefix}/${MqttClient.CLIENTS_PREFIX}/${this._clientID}/${suffix}`;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Method used to close clients connection, use this before destroy
|
|
60
|
+
*/
|
|
61
|
+
close() {
|
|
62
|
+
return new Promise((resolve) => {
|
|
63
|
+
if (this.closed) {
|
|
64
|
+
resolve();
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
this.closed = true;
|
|
68
|
+
if (this.retrySubTimeout) {
|
|
69
|
+
clearTimeout(this.retrySubTimeout);
|
|
70
|
+
this.retrySubTimeout = null;
|
|
71
|
+
}
|
|
72
|
+
let resolved = false;
|
|
73
|
+
if (this.client) {
|
|
74
|
+
const onClose = async (error) => {
|
|
75
|
+
// prevent multiple resolve
|
|
76
|
+
if (resolved) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
resolved = true;
|
|
80
|
+
// fix error:Failed to lock DB file when force closing
|
|
81
|
+
await this.storeManager?.close();
|
|
82
|
+
if (this._closeTimeout) {
|
|
83
|
+
clearTimeout(this._closeTimeout);
|
|
84
|
+
this._closeTimeout = null;
|
|
85
|
+
}
|
|
86
|
+
if (error) {
|
|
87
|
+
logger.error('Error while closing client', error);
|
|
88
|
+
}
|
|
89
|
+
this.removeAllListeners();
|
|
90
|
+
logger.info('Client closed');
|
|
91
|
+
resolve();
|
|
92
|
+
};
|
|
93
|
+
this.client.end(false, {}, onClose);
|
|
94
|
+
// in case a clean close doesn't work, force close
|
|
95
|
+
this._closeTimeout = setTimeout(() => {
|
|
96
|
+
this.client.end(true, {}, onClose);
|
|
97
|
+
}, 5000);
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
this.removeAllListeners();
|
|
101
|
+
resolve();
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Method used to get status
|
|
107
|
+
*/
|
|
108
|
+
getStatus() {
|
|
109
|
+
const status = {};
|
|
110
|
+
status.status = this.client && this.client.connected;
|
|
111
|
+
status.error = this.error || 'Offline';
|
|
112
|
+
status.config = this.config;
|
|
113
|
+
return status;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Method used to update client connection status
|
|
117
|
+
*/
|
|
118
|
+
updateClientStatus(connected) {
|
|
119
|
+
if (this.client) {
|
|
120
|
+
this.client.publish(this.getClientTopic(MqttClient.STATUS_TOPIC), JSON.stringify({ value: connected, time: Date.now() }), { retain: this.config.retain, qos: this.config.qos });
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Method used to publish app version to mqtt
|
|
125
|
+
*/
|
|
126
|
+
publishVersion() {
|
|
127
|
+
if (this.client) {
|
|
128
|
+
this.client.publish(this.getClientTopic(MqttClient.VERSION_TOPIC), JSON.stringify({ value: utils_1.pkgJson.version, time: Date.now() }), { retain: this.config.retain, qos: this.config.qos });
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Method used to update client
|
|
133
|
+
*/
|
|
134
|
+
async update(config) {
|
|
135
|
+
await this.close();
|
|
136
|
+
logger.info('Restarting Mqtt Client after update...');
|
|
137
|
+
await this._init(config);
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Method used to subscribe tags for write requests
|
|
141
|
+
*/
|
|
142
|
+
subscribe(topic, options = {
|
|
143
|
+
qos: 1,
|
|
144
|
+
addPrefix: true,
|
|
145
|
+
}) {
|
|
146
|
+
return new Promise((resolve, reject) => {
|
|
147
|
+
const subOptions = {
|
|
148
|
+
qos: options.qos,
|
|
149
|
+
};
|
|
150
|
+
topic = options.addPrefix
|
|
151
|
+
? this.config.prefix + '/' + topic + '/set'
|
|
152
|
+
: topic;
|
|
153
|
+
options.addPrefix = false; // in case of retry, don't add again the prefix
|
|
154
|
+
if (this.client && this.client.connected) {
|
|
155
|
+
logger.log('debug', `Subscribing to ${topic} with options %o`, subOptions);
|
|
156
|
+
this.client.subscribe(topic, subOptions, (err, granted) => {
|
|
157
|
+
if (err) {
|
|
158
|
+
logger.error(`Error subscribing to ${topic}`, err);
|
|
159
|
+
this.toSubscribe.set(topic, options);
|
|
160
|
+
reject(err);
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
for (const res of granted) {
|
|
164
|
+
if (res.qos === 128) {
|
|
165
|
+
logger.error(`Error subscribing to ${topic}, client doesn't have permission to subscribe to it`);
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
logger.info(`Subscribed to ${topic}`);
|
|
169
|
+
}
|
|
170
|
+
this.toSubscribe.delete(topic);
|
|
171
|
+
}
|
|
172
|
+
resolve();
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
logger.debug(`Client not connected yet, subscribing to ${topic} later...`);
|
|
178
|
+
this.toSubscribe.set(topic, options);
|
|
179
|
+
reject(Error('Client not connected'));
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Method used to publish an update
|
|
185
|
+
*/
|
|
186
|
+
publish(topic, data, options, prefix) {
|
|
187
|
+
if (this.client) {
|
|
188
|
+
const settingOptions = {
|
|
189
|
+
qos: this.config.qos,
|
|
190
|
+
retain: this.config.retain,
|
|
191
|
+
};
|
|
192
|
+
// by default use settingsOptions
|
|
193
|
+
options = Object.assign(settingOptions, options);
|
|
194
|
+
topic = (prefix || this.config.prefix) + '/' + topic;
|
|
195
|
+
logger.log('debug', 'Publishing to %s: %o with options %o', topic, data, options);
|
|
196
|
+
this.client.publish(topic, JSON.stringify(data), options, function (err) {
|
|
197
|
+
if (err) {
|
|
198
|
+
logger.error(`Error while publishing a value ${err.message}`);
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
} // end if client
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Method used to get the topic with prefix/suffix
|
|
205
|
+
*/
|
|
206
|
+
getTopic(topic, set = false) {
|
|
207
|
+
return this.config.prefix + '/' + topic + (set ? '/set' : '');
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Initialize client
|
|
211
|
+
*/
|
|
212
|
+
async _init(config) {
|
|
213
|
+
this.config = config;
|
|
214
|
+
this.toSubscribe = new Map();
|
|
215
|
+
if (!config || config.disabled) {
|
|
216
|
+
logger.info('MQTT is disabled');
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
this._clientID = (0, utils_1.sanitizeTopic)(MqttClient.NAME_PREFIX + (process.env.MQTT_NAME || config.name));
|
|
220
|
+
const parsed = url.parse(config.host || '');
|
|
221
|
+
let protocol = 'mqtt';
|
|
222
|
+
if (parsed.protocol)
|
|
223
|
+
protocol = parsed.protocol.replace(/:$/, '');
|
|
224
|
+
const options = {
|
|
225
|
+
clientId: this._clientID,
|
|
226
|
+
reconnectPeriod: config.reconnectPeriod,
|
|
227
|
+
clean: config.clean,
|
|
228
|
+
rejectUnauthorized: !config.allowSelfsigned,
|
|
229
|
+
will: {
|
|
230
|
+
topic: this.getClientTopic(MqttClient.STATUS_TOPIC),
|
|
231
|
+
payload: JSON.stringify({ value: false }),
|
|
232
|
+
qos: this.config.qos,
|
|
233
|
+
retain: this.config.retain,
|
|
234
|
+
},
|
|
235
|
+
};
|
|
236
|
+
if (['mqtts', 'wss', 'wxs', 'alis', 'tls'].indexOf(protocol) >= 0) {
|
|
237
|
+
if (!config.allowSelfsigned)
|
|
238
|
+
options.ca = config._ca;
|
|
239
|
+
if (config._key) {
|
|
240
|
+
options.key = config._key;
|
|
241
|
+
}
|
|
242
|
+
if (config._cert) {
|
|
243
|
+
options.cert = config._cert;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
if (config.store) {
|
|
247
|
+
const dbDir = (0, path_1.join)(app_1.storeDir, 'mqtt-packets-store');
|
|
248
|
+
await (0, fs_extra_1.ensureDir)(dbDir);
|
|
249
|
+
this.storeManager = new mqtt_jsonl_store_1.Manager(dbDir);
|
|
250
|
+
await this.storeManager.open();
|
|
251
|
+
// no reason to use a memory store for incoming messages
|
|
252
|
+
options.incomingStore = this.storeManager
|
|
253
|
+
.incoming;
|
|
254
|
+
options.outgoingStore = this.storeManager
|
|
255
|
+
.outgoing;
|
|
256
|
+
}
|
|
257
|
+
if (config.auth) {
|
|
258
|
+
options.username = config.username;
|
|
259
|
+
options.password = config.password;
|
|
260
|
+
}
|
|
261
|
+
try {
|
|
262
|
+
const serverUrl = `${protocol}://${parsed.hostname || config.host}:${config.port}`;
|
|
263
|
+
logger.info(`Connecting to ${serverUrl}`);
|
|
264
|
+
const client = (0, mqtt_1.connect)(serverUrl, options);
|
|
265
|
+
this.client = client;
|
|
266
|
+
client.on('connect', this._onConnect.bind(this));
|
|
267
|
+
client.on('message', this._onMessageReceived.bind(this));
|
|
268
|
+
client.on('reconnect', this._onReconnect.bind(this));
|
|
269
|
+
client.on('close', this._onClose.bind(this));
|
|
270
|
+
client.on('error', this._onError.bind(this));
|
|
271
|
+
client.on('offline', this._onOffline.bind(this));
|
|
272
|
+
}
|
|
273
|
+
catch (e) {
|
|
274
|
+
logger.error(`Error while connecting MQTT ${e.message}`);
|
|
275
|
+
this.error = e.message;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Function called when MQTT client connects
|
|
280
|
+
*/
|
|
281
|
+
async _onConnect() {
|
|
282
|
+
logger.info('MQTT client connected');
|
|
283
|
+
this.emit('connect');
|
|
284
|
+
const subscribePromises = [];
|
|
285
|
+
subscribePromises.push(this.subscribe(MqttClient.HASS_WILL, { addPrefix: false, qos: 1 }));
|
|
286
|
+
// subscribe to actions
|
|
287
|
+
for (let i = 0; i < MqttClient.ACTIONS.length; i++) {
|
|
288
|
+
subscribePromises.push(this.subscribe([
|
|
289
|
+
this.config.prefix,
|
|
290
|
+
MqttClient.CLIENTS_PREFIX,
|
|
291
|
+
this._clientID,
|
|
292
|
+
MqttClient.ACTIONS[i],
|
|
293
|
+
'#',
|
|
294
|
+
].join('/'), { addPrefix: false, qos: 1 }));
|
|
295
|
+
}
|
|
296
|
+
await (0, utils_1.allSettled)(subscribePromises);
|
|
297
|
+
await this._retrySubscribe();
|
|
298
|
+
this.emit('brokerStatus', true);
|
|
299
|
+
this.publishVersion();
|
|
300
|
+
// Update client status
|
|
301
|
+
this.updateClientStatus(true);
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Function called when MQTT client reconnects
|
|
305
|
+
*/
|
|
306
|
+
_onReconnect() {
|
|
307
|
+
logger.info('MQTT client reconnecting');
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* Function called when MQTT client reconnects
|
|
311
|
+
*/
|
|
312
|
+
_onError(error) {
|
|
313
|
+
logger.error('Mqtt client error', error);
|
|
314
|
+
this.error = error.message;
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Function called when MQTT client go offline
|
|
318
|
+
*/
|
|
319
|
+
_onOffline() {
|
|
320
|
+
if (this.retrySubTimeout) {
|
|
321
|
+
clearTimeout(this.retrySubTimeout);
|
|
322
|
+
this.retrySubTimeout = null;
|
|
323
|
+
}
|
|
324
|
+
logger.info('MQTT client offline');
|
|
325
|
+
this.emit('brokerStatus', false);
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* Function called when MQTT client is closed
|
|
329
|
+
*/
|
|
330
|
+
_onClose() {
|
|
331
|
+
logger.info('MQTT client closed');
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* Function called when an MQTT message is received
|
|
335
|
+
*/
|
|
336
|
+
_onMessageReceived(topic, payload) {
|
|
337
|
+
if (this.closed)
|
|
338
|
+
return;
|
|
339
|
+
let parsed = payload?.toString();
|
|
340
|
+
logger.log('info', `Message received on ${topic}: %o`, parsed);
|
|
341
|
+
if (topic === MqttClient.HASS_WILL) {
|
|
342
|
+
if (typeof parsed === 'string') {
|
|
343
|
+
this.emit('hassStatus', parsed.toLowerCase() === 'online');
|
|
344
|
+
}
|
|
345
|
+
else {
|
|
346
|
+
logger.error('Invalid payload sent to Hass Will topic');
|
|
347
|
+
}
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
// remove prefix
|
|
351
|
+
topic = topic.substring(this.config.prefix.length + 1);
|
|
352
|
+
const parts = topic.split('/');
|
|
353
|
+
// It's not a write request
|
|
354
|
+
if (parts.pop() !== 'set')
|
|
355
|
+
return;
|
|
356
|
+
if (isNaN(parseInt(parsed))) {
|
|
357
|
+
try {
|
|
358
|
+
parsed = (0, utils_1.parseJSON)(parsed);
|
|
359
|
+
}
|
|
360
|
+
catch (e) {
|
|
361
|
+
// it' ok fallback to string
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
else {
|
|
365
|
+
parsed = Number(parsed);
|
|
366
|
+
}
|
|
367
|
+
// It's an action
|
|
368
|
+
if (parts[0] === MqttClient.CLIENTS_PREFIX) {
|
|
369
|
+
if (parts.length < 3)
|
|
370
|
+
return;
|
|
371
|
+
const action = MqttClient.ACTIONS.indexOf(parts[2]);
|
|
372
|
+
switch (action) {
|
|
373
|
+
case 0: // broadcast
|
|
374
|
+
this.emit('broadcastRequest', parts.slice(3), parsed);
|
|
375
|
+
// publish back to give a feedback the action has been received
|
|
376
|
+
// same topic without /set suffix
|
|
377
|
+
this.publish(parts.join('/'), parsed);
|
|
378
|
+
break;
|
|
379
|
+
case 1: // api
|
|
380
|
+
this.emit('apiCall', parts.join('/'), parts[3], parsed);
|
|
381
|
+
break;
|
|
382
|
+
case 2: // multicast
|
|
383
|
+
this.emit('multicastRequest', parsed);
|
|
384
|
+
// publish back to give a feedback the action has been received
|
|
385
|
+
// same topic without /set suffix
|
|
386
|
+
this.publish(parts.join('/'), parsed);
|
|
387
|
+
break;
|
|
388
|
+
default:
|
|
389
|
+
logger.warn(`Unknown action received ${action} ${topic}`);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
else {
|
|
393
|
+
// It's a write request on zwave network
|
|
394
|
+
this.emit('writeRequest', parts, parsed);
|
|
395
|
+
}
|
|
396
|
+
} // end onMessageReceived
|
|
397
|
+
async _retrySubscribe() {
|
|
398
|
+
if (this.retrySubTimeout) {
|
|
399
|
+
clearTimeout(this.retrySubTimeout);
|
|
400
|
+
this.retrySubTimeout = null;
|
|
401
|
+
}
|
|
402
|
+
if (this.toSubscribe.size === 0) {
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
logger.debug('Retry to subscribe to topics...');
|
|
406
|
+
const subscribePromises = [];
|
|
407
|
+
const topics = this.toSubscribe.keys();
|
|
408
|
+
for (const t of topics) {
|
|
409
|
+
subscribePromises.push(this.subscribe(t, this.toSubscribe.get(t)));
|
|
410
|
+
}
|
|
411
|
+
this.toSubscribe = new Map();
|
|
412
|
+
await (0, utils_1.allSettled)(subscribePromises);
|
|
413
|
+
if (this.toSubscribe.size > 0) {
|
|
414
|
+
this.retrySubTimeout = setTimeout(this._retrySubscribe.bind(this), 5000);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
exports.default = MqttClient;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.inboundEvents = exports.socketEvents = void 0;
|
|
4
|
+
var socketEvents;
|
|
5
|
+
(function (socketEvents) {
|
|
6
|
+
socketEvents["init"] = "INIT";
|
|
7
|
+
socketEvents["controller"] = "CONTROLLER_CMD";
|
|
8
|
+
socketEvents["connected"] = "CONNECTED";
|
|
9
|
+
socketEvents["nodeFound"] = "NODE_FOUND";
|
|
10
|
+
socketEvents["nodeAdded"] = "NODE_ADDED";
|
|
11
|
+
socketEvents["nodeRemoved"] = "NODE_REMOVED";
|
|
12
|
+
socketEvents["nodeUpdated"] = "NODE_UPDATED";
|
|
13
|
+
socketEvents["valueUpdated"] = "VALUE_UPDATED";
|
|
14
|
+
socketEvents["valueRemoved"] = "VALUE_REMOVED";
|
|
15
|
+
socketEvents["metadataUpdated"] = "METADATA_UPDATED";
|
|
16
|
+
socketEvents["rebuildRoutesProgress"] = "REBUILD_ROUTES_PROGRESS";
|
|
17
|
+
socketEvents["healthCheckProgress"] = "HEALTH_CHECK_PROGRESS";
|
|
18
|
+
socketEvents["info"] = "INFO";
|
|
19
|
+
socketEvents["api"] = "API_RETURN";
|
|
20
|
+
socketEvents["debug"] = "DEBUG";
|
|
21
|
+
socketEvents["statistics"] = "STATISTICS";
|
|
22
|
+
socketEvents["nodeEvent"] = "NODE_EVENT";
|
|
23
|
+
socketEvents["grantSecurityClasses"] = "GRANT_SECURITY_CLASSES";
|
|
24
|
+
socketEvents["validateDSK"] = "VALIDATE_DSK";
|
|
25
|
+
socketEvents["inclusionAborted"] = "INCLUSION_ABORTED";
|
|
26
|
+
})(socketEvents || (exports.socketEvents = socketEvents = {}));
|
|
27
|
+
// events from client ---> server
|
|
28
|
+
var inboundEvents;
|
|
29
|
+
(function (inboundEvents) {
|
|
30
|
+
inboundEvents["init"] = "INITED";
|
|
31
|
+
inboundEvents["zwave"] = "ZWAVE_API";
|
|
32
|
+
inboundEvents["hass"] = "HASS_API";
|
|
33
|
+
inboundEvents["mqtt"] = "MQTT_API";
|
|
34
|
+
})(inboundEvents || (exports.inboundEvents = inboundEvents = {}));
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const logger_1 = require("./logger");
|
|
4
|
+
const socket_io_1 = require("socket.io");
|
|
5
|
+
const EventEmitter_1 = require("./EventEmitter");
|
|
6
|
+
const SocketEvents_1 = require("./SocketEvents");
|
|
7
|
+
const logger = (0, logger_1.module)('Socket');
|
|
8
|
+
/**
|
|
9
|
+
* The constructor
|
|
10
|
+
*/
|
|
11
|
+
class SocketManager extends EventEmitter_1.TypedEventEmitter {
|
|
12
|
+
io;
|
|
13
|
+
activeSockets = new Map();
|
|
14
|
+
authMiddleware;
|
|
15
|
+
/**
|
|
16
|
+
* Binds socket.io to `server`
|
|
17
|
+
*
|
|
18
|
+
*/
|
|
19
|
+
bindServer(server) {
|
|
20
|
+
this.io = new socket_io_1.Server(server, {
|
|
21
|
+
path: '/socket.io',
|
|
22
|
+
});
|
|
23
|
+
this.io.on('error', (err) => {
|
|
24
|
+
logger.error(`Socket error: ${err.message}`);
|
|
25
|
+
});
|
|
26
|
+
this.io
|
|
27
|
+
.use(this._authMiddleware())
|
|
28
|
+
.on('connection', this._onConnection.bind(this));
|
|
29
|
+
}
|
|
30
|
+
_authMiddleware() {
|
|
31
|
+
return (socket, next) => {
|
|
32
|
+
if (this.authMiddleware !== undefined) {
|
|
33
|
+
this.authMiddleware(socket, next);
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
next();
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Handles new socket connections
|
|
42
|
+
*
|
|
43
|
+
*/
|
|
44
|
+
_onConnection(socket) {
|
|
45
|
+
logger.debug(`New connection ${socket.id}`);
|
|
46
|
+
// add socket to active sockets
|
|
47
|
+
this.activeSockets.set(socket.id, socket);
|
|
48
|
+
this.emit('clients', 'connection', this.activeSockets);
|
|
49
|
+
// register inbound events from this socket
|
|
50
|
+
for (const k in SocketEvents_1.inboundEvents) {
|
|
51
|
+
const eventName = SocketEvents_1.inboundEvents[k];
|
|
52
|
+
// pass socket reference as first parameter
|
|
53
|
+
socket.on(eventName, this._emitEvent.bind(this, eventName, socket));
|
|
54
|
+
}
|
|
55
|
+
// https://socket.io/docs/v4/server-socket-instance/#events
|
|
56
|
+
socket.on('disconnect', (reason) => {
|
|
57
|
+
logger.debug(`User disconnected from ${socket.id}: ${reason}`);
|
|
58
|
+
this.activeSockets.delete(socket.id);
|
|
59
|
+
this.emit('clients', 'disconnect', this.activeSockets);
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Logs and emits the `eventName` with `socket` and `args` as parameters
|
|
64
|
+
*
|
|
65
|
+
*/
|
|
66
|
+
_emitEvent(eventName, socket, data) {
|
|
67
|
+
logger.debug(`Event ${eventName} emitted to ${socket.id}`);
|
|
68
|
+
this.emit(eventName, socket, data);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
exports.default = SocketManager;
|