@evops/lightwaverf 0.0.6 → 0.0.7

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/dist/index.js ADDED
@@ -0,0 +1,505 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
5
+ }) : (function(o, m, k, k2) {
6
+ if (k2 === undefined) k2 = k;
7
+ o[k2] = m[k];
8
+ }));
9
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
10
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
11
+ }) : function(o, v) {
12
+ o["default"] = v;
13
+ });
14
+ var __importStar = (this && this.__importStar) || function (mod) {
15
+ if (mod && mod.__esModule) return mod;
16
+ var result = {};
17
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
18
+ __setModuleDefault(result, mod);
19
+ return result;
20
+ };
21
+ var __importDefault = (this && this.__importDefault) || function (mod) {
22
+ return (mod && mod.__esModule) ? mod : { "default": mod };
23
+ };
24
+ Object.defineProperty(exports, "__esModule", { value: true });
25
+ const util_1 = __importDefault(require("util"));
26
+ const events_1 = __importStar(require("events"));
27
+ const dgram_1 = __importDefault(require("dgram"));
28
+ const request_promise_1 = __importDefault(require("request-promise"));
29
+ const debug_1 = __importDefault(require("debug"));
30
+ const debug = (0, debug_1.default)('lightwaverf');
31
+ class LightwaveRFConfiguration {
32
+ constructor() {
33
+ this.timeout = 1000;
34
+ }
35
+ }
36
+ /**
37
+ * LightwaveRF API
38
+ *
39
+ * @param object config The config
40
+ *
41
+ * An instance of the LightwaveRF API
42
+ */
43
+ class LightwaveRF extends events_1.EventEmitter {
44
+ constructor(config, callback) {
45
+ super();
46
+ this.timeout = 1000;
47
+ this.queue = [];
48
+ this.ready = true;
49
+ this.awaitRegistrration = false;
50
+ this.currentTransactionNumber = 4674773;
51
+ this.devices = [];
52
+ this.messageCounter = 0;
53
+ this.config = {};
54
+ this.responseListeners = new Map();
55
+ const self = this;
56
+ this.timeout = config.timeout || 1000;
57
+ this.devices = []; //[{roomId:0,roomName:'',
58
+ //deviceId:0,deviceName:'',
59
+ //deviceType:''}];
60
+ this.setMaxListeners(255);
61
+ //Counter
62
+ this.messageCounter = 0;
63
+ //Config
64
+ this.config = config;
65
+ // Use broadcast to discover the IP
66
+ this.config.ip = this.config.ip || '255.255.255.255';
67
+ //Send Socket
68
+ this.sendSocket = dgram_1.default.createSocket("udp4");
69
+ //Receive socket
70
+ this.receiveSocket = dgram_1.default.createSocket("udp4");
71
+ //Receive message
72
+ this.receiveSocket.on("message", function (message, rinfo) {
73
+ // If we were using broadcast IP, we have now
74
+ // discovered Link device IP and can switch off
75
+ // broadcast
76
+ if (self.config.ip == '255.255.255.255') {
77
+ console.log("We have now discovered Link IP address: %s", rinfo.address);
78
+ self.config.ip = rinfo.address;
79
+ self.sendSocket.setBroadcast(false);
80
+ }
81
+ //Check this came from the lightwave unit
82
+ if (rinfo.address !== self.config.ip) {
83
+ //Came from wrong ip]
84
+ console.warn("Response came from a different IP than our configured", rinfo.address, self.config.ip);
85
+ return false;
86
+ }
87
+ const parseResponse = (buffer) => {
88
+ const response = new Object();
89
+ const message = buffer.toString('utf-8');
90
+ if (message.match(/^\*!/)) {
91
+ const jsonResponse = JSON.parse(message.replace(/^\*!/, ''));
92
+ self.currentTransactionNumber = jsonResponse.trans + 1;
93
+ Object.assign(response, jsonResponse);
94
+ }
95
+ else {
96
+ //Split off the code for the message
97
+ var parts = message.split(",");
98
+ var trans = parts.splice(0, 1);
99
+ var content = parts.join(",").replace(/(\r\n|\n|\r)/gm, "");
100
+ response.trans = parseInt(trans[0]);
101
+ response.message = content;
102
+ }
103
+ response.trans = response.trans !== null ? parseInt(response.trans) : null;
104
+ response.error = response.pkt === "error" ? response.fn : null;
105
+ return response;
106
+ };
107
+ let linkResponse = parseResponse(message);
108
+ debug(">>>>>>>> Received response msg: %s, response: %s, rinfo: %s", message, linkResponse, rinfo);
109
+ if (linkResponse.error === "nonRegistered" && !self.awaitRegistrration) {
110
+ console.warn("Your device is not registered, please accept registration on the Link devices");
111
+ self.register(() => { });
112
+ }
113
+ if (linkResponse.msg === "success" && linkResponse.pairType) {
114
+ self.awaitRegistrration = false;
115
+ }
116
+ debug("Link response fn", linkResponse.fn);
117
+ if (linkResponse.fn === "on") {
118
+ self.emit("deviceTurnedOn", linkResponse.room, linkResponse.dev);
119
+ }
120
+ if (linkResponse.fn === "off") {
121
+ self.emit("deviceTurnedOff", linkResponse.room, linkResponse.dev);
122
+ }
123
+ debug(self.responseListeners);
124
+ var responseListenerData = self.responseListeners.get(linkResponse.trans);
125
+ if (!responseListenerData) {
126
+ debug("We haven't got anyone to respond to, ignoring the message");
127
+ return;
128
+ }
129
+ responseListenerData.listener(linkResponse.error ? linkResponse.error : null, linkResponse.fn);
130
+ self.responseListeners.delete(linkResponse.trans);
131
+ });
132
+ this.receiveSocket.on("listening", function () {
133
+ var address = self.receiveSocket.address();
134
+ debug("Receiver socket listening " + address.address + ":" + address.port);
135
+ self.send('@H', (code, err) => {
136
+ if (err) {
137
+ console.log('code', code, 'error', err);
138
+ return;
139
+ }
140
+ self.initialiseConfiguration(callback);
141
+ });
142
+ });
143
+ this.sendSocket.bind();
144
+ this.sendSocket.on('listening', () => {
145
+ debug("Send socket is ready");
146
+ debug("Setting up receiver socket");
147
+ //Bind to the receive port
148
+ self.receiveSocket.bind(9761);
149
+ });
150
+ process.on('SIGINT', () => {
151
+ self.stop();
152
+ });
153
+ }
154
+ stop() {
155
+ debug("Stopping server sockets");
156
+ this.sendSocket.close();
157
+ this.receiveSocket.close();
158
+ }
159
+ initialiseConfiguration(callback) {
160
+ //Check config
161
+ if (!this.config.host) {
162
+ this.config.host = "web.trustsmartcloud.com";
163
+ }
164
+ if (!this.config.email || !this.config.pin) {
165
+ callback(null, "No email or pin specified. The server configuration (rooms, devices, etc.) cannot be obtained");
166
+ this.stop();
167
+ }
168
+ else {
169
+ this.getConfiguration(this.config.email, this.config.pin, this.config.host, callback);
170
+ }
171
+ }
172
+ /**
173
+ * Register this device with the Wi-Fi Link
174
+ *
175
+ * @param Function callback The callback function
176
+ *
177
+ * @return void
178
+ */
179
+ register(callback) {
180
+ this.awaitRegistrration = true;
181
+ this.sendUdp("!F*p", callback);
182
+ }
183
+ /**
184
+ * Request energy
185
+ *
186
+ * @param Function callback The callback function
187
+ *
188
+ * @return void
189
+ */
190
+ requestEnergy(callback) {
191
+ this.sendUdp("@?\0", function (error, content) {
192
+ if (error) {
193
+ //Send error back
194
+ callback(error);
195
+ }
196
+ else {
197
+ //Determine if this is the energy monitor
198
+ //ID,?W=current,max,today,yesterday (all kwh)
199
+ var values = content.substring(3).split(",");
200
+ callback(undefined, {
201
+ current: parseInt(values[0], 10),
202
+ max: parseInt(values[1], 10),
203
+ today: parseInt(values[2], 10),
204
+ yesterday: parseInt(values[3], 10)
205
+ });
206
+ }
207
+ });
208
+ }
209
+ /**
210
+ * Turn a device off
211
+ *
212
+ * @param integer roomId The room ID
213
+ * @param integer deviceId The device ID
214
+ * @param Function callback The callback for if there are any errors
215
+ *
216
+ * @return void
217
+ */
218
+ turnDeviceOff(roomId, deviceId, callback) {
219
+ var state = "0";
220
+ this.exec("!R" + roomId + "D" + deviceId + "F" + state + "|\0", callback);
221
+ }
222
+ /**
223
+ * Turn a device on
224
+ *
225
+ * @param integer roomId The room ID
226
+ * @param integer deviceId The device ID
227
+ * @param Function callback The callback for if there are any errors
228
+ *
229
+ * @return void
230
+ */
231
+ turnDeviceOn(roomId, deviceId, callback) {
232
+ var state = "1";
233
+ this.exec("!R" + roomId + "D" + deviceId + "F" + state + "|\0", callback);
234
+ }
235
+ /**
236
+ * Open a device
237
+ *
238
+ * @param integer roomId The room ID
239
+ * @param integer deviceId The device ID
240
+ * @param Function callback The callback for if there are any errors
241
+ *
242
+ * @return void
243
+ */
244
+ openDevice(roomId, deviceId, callback) {
245
+ var state = ">";
246
+ this.exec("!R" + roomId + "D" + deviceId + "F" + state + "|\0", callback);
247
+ }
248
+ /**
249
+ * Close a device
250
+ *
251
+ * @param integer roomId The room ID
252
+ * @param integer deviceId The device ID
253
+ * @param Function callback The callback for if there are any errors
254
+ *
255
+ * @return void
256
+ */
257
+ closeDevice(roomId, deviceId, callback) {
258
+ var state = "<";
259
+ this.exec("!R" + roomId + "D" + deviceId + "F" + state + "|\0", callback);
260
+ }
261
+ /**
262
+ * Stop a device
263
+ *
264
+ * @param integer roomId The room ID
265
+ * @param integer deviceId The device ID
266
+ * @param Function callback The callback for if there are any errors
267
+ *
268
+ * @return void
269
+ */
270
+ stopDevice(roomId, deviceId, callback) {
271
+ var state = "^";
272
+ this.exec("!R" + roomId + "D" + deviceId + "F" + state + "|\0", callback);
273
+ }
274
+ /**
275
+ * Turn all devices in a room off
276
+ *
277
+ * @param integer roomId The room ID
278
+ * @param Function callback The callback for if there are any errors
279
+ *
280
+ * @return void
281
+ */
282
+ turnRoomOff(roomId, callback) {
283
+ this.exec("!R" + roomId + "Fa\0", callback);
284
+ }
285
+ /**
286
+ * Set the dim percentage of a device
287
+ *
288
+ * @param integer roomId The room ID
289
+ * @param integer deviceId The device ID
290
+ * @param integer dimPercentage The percentage to set the device dim
291
+ * @param Function callback The callback for if there are any errors
292
+ *
293
+ * @return void
294
+ */
295
+ setDeviceDim(roomId, deviceId, dimPercentage, callback) {
296
+ var dimAmount = dimPercentage * 0.32; //Dim is on a scale from 0 to 32
297
+ if (dimAmount === 0) {
298
+ this.turnDeviceOff(roomId, deviceId, callback);
299
+ }
300
+ else {
301
+ this.exec("!R" + roomId + "D" + deviceId + "FdP" + dimAmount + "|\0", callback);
302
+ }
303
+ }
304
+ /**
305
+ * Get message code
306
+ *
307
+ * @return string
308
+ */
309
+ getTransactionNumber() {
310
+ return this.currentTransactionNumber;
311
+ }
312
+ exec(...args) {
313
+ // Check if the queue has a reasonable size
314
+ if (this.queue.length > 100) {
315
+ this.queue.pop();
316
+ }
317
+ debug("Ading to queue: " + args.join(" "));
318
+ this.queue.push(args);
319
+ this.process();
320
+ }
321
+ ;
322
+ send(cmd, callback) {
323
+ this.sendUdp(cmd, callback);
324
+ //if (callback) callback();
325
+ }
326
+ ;
327
+ /**
328
+ * Send a message over udp
329
+ *
330
+ * @param string message The message to send
331
+ * @param Function callback The callback for if there are any errors
332
+ *
333
+ * @return void
334
+ */
335
+ sendUdp(message, callback) {
336
+ //Add to message
337
+ const transactionNumber = this.getTransactionNumber();
338
+ //Prepend code to message
339
+ message = `${transactionNumber},${message}`;
340
+ debug(`[${this.config.ip}][trans: ${transactionNumber}] Sending message: ${message}`);
341
+ //Create buffer from message
342
+ const messageBuffer = Buffer.from(message, 'utf-8');
343
+ this.sendSocket.setBroadcast(true);
344
+ debug("Callback for message: " + message, callback);
345
+ //Add listener
346
+ if (callback) {
347
+ debug("Registering call back with transaction number: " + transactionNumber);
348
+ this.responseListeners.set(transactionNumber, {
349
+ time: new Date().getTime(),
350
+ listener: callback
351
+ });
352
+ debug(this.responseListeners);
353
+ // Expire request, trigger retry
354
+ setTimeout(() => {
355
+ const listener = this.responseListeners.get(transactionNumber);
356
+ if (listener) {
357
+ debug("The listener is still there, triggering error");
358
+ this.responseListeners.delete(transactionNumber);
359
+ callback("ERR:EXPIRED", undefined);
360
+ }
361
+ }, 1000);
362
+ }
363
+ //Broadcast the message
364
+ this.sendSocket.send(messageBuffer, 0, messageBuffer.length, 9760, this.config.ip);
365
+ }
366
+ process() {
367
+ debug("Checking queue");
368
+ if (this.queue.length === 0)
369
+ return;
370
+ if (!this.ready)
371
+ return;
372
+ var self = this;
373
+ this.ready = false;
374
+ debug("Processing queue...");
375
+ debug("Items in the queue", this.queue.length);
376
+ this.send.apply(this, this.queue.shift());
377
+ setTimeout(function () {
378
+ self.ready = true;
379
+ self.process();
380
+ }, this.timeout);
381
+ }
382
+ ;
383
+ /**
384
+ * Parser to get de devices from https POST
385
+ */
386
+ getDevices(roomsString, devicesString, typesString, callback) {
387
+ var nrRooms = 8;
388
+ var nrDevicesPerRoom = 10;
389
+ var tempRS = roomsString;
390
+ var tempDS = devicesString;
391
+ var tempTS = typesString;
392
+ var deviceCounter = 0;
393
+ for (var i = 0; i < nrRooms; i++) {
394
+ var rId = i + 1;
395
+ tempRS = tempRS.substring(tempRS.indexOf('\"') + 1);
396
+ var rName = tempRS.substring(0, tempRS.indexOf('\"'));
397
+ tempRS = tempRS.substring(tempRS.indexOf('\"') + 1);
398
+ //console.log("room=" + rName);
399
+ for (var j = 0; j < nrDevicesPerRoom; j++) {
400
+ var dId = j + 1;
401
+ tempDS = tempDS.substring(tempDS.indexOf('\"') + 1);
402
+ var dName = tempDS.substring(0, tempDS.indexOf('\"'));
403
+ tempDS = tempDS.substring(tempDS.indexOf('\"') + 1);
404
+ //console.log("devices=" + dName);
405
+ tempTS = tempTS.substring(tempTS.indexOf('\"') + 1);
406
+ var dType = tempTS.substring(0, tempTS.indexOf('\"'));
407
+ tempTS = tempTS.substring(tempTS.indexOf('\"') + 1);
408
+ //console.log("devices=" + deviceName + " type=" + dType);
409
+ // Get device types
410
+ // O: On/Off Switch
411
+ // D: Dimmer
412
+ // R: Radiator(s)
413
+ // P: Open/Close
414
+ // I: Inactive (i.e. not configured)
415
+ // m: Mood (inactive)
416
+ // M: Mood (active)
417
+ // o: All Off
418
+ if (dType == "O" || dType == "D") {
419
+ this.devices.push({
420
+ roomId: rId, roomName: rName,
421
+ deviceId: dId, deviceName: dName,
422
+ deviceType: dType
423
+ });
424
+ //console.log("devices=" + deviceName + " type=" + deviceType);
425
+ deviceCounter += 1;
426
+ }
427
+ }
428
+ }
429
+ if (callback)
430
+ callback(this.devices, this);
431
+ //console.log(this.devices);
432
+ }
433
+ parseRooms(lightwaveResponse, callback) {
434
+ debug('Parsing lightwaveResponse: ', lightwaveResponse.content.estates[0].locations[0].zones[0].rooms[0].devices);
435
+ var home = lightwaveResponse.content.estates[0].locations[0].zones[0];
436
+ for (var i = 0; i < home.rooms.length; i++) {
437
+ var r = home.rooms[i];
438
+ debug("Room " + r.name + " with " + r.devices.length + " devices");
439
+ // Get device types
440
+ // O: On/Off Switch
441
+ // D: Dimmer
442
+ // R: Radiator(s)
443
+ // P: Open/Close
444
+ // I: Inactive (i.e. not configured)
445
+ // m: Mood (inactive)
446
+ // M: Mood (active)
447
+ // o: All Off
448
+ var deviceTypeMapping = {
449
+ 1: 'L',
450
+ 2: 'D',
451
+ 3: 'P'
452
+ };
453
+ for (var j = 0; j < r.devices.length; j++) {
454
+ var d = r.devices[j];
455
+ const device = {
456
+ roomId: r.room_number,
457
+ roomName: r.name,
458
+ deviceId: d.device_number,
459
+ deviceName: d.name,
460
+ deviceType: d.device_type_id
461
+ };
462
+ this.devices.push(device);
463
+ }
464
+ }
465
+ debug('Devices:', this.devices);
466
+ callback(this.devices, null);
467
+ }
468
+ ;
469
+ /**
470
+ * Connect to the server and obtain the configuration
471
+ */
472
+ getConfiguration(email, pin, manager_host, callback) {
473
+ // An object of options to indicate where to post to
474
+ debug('Getting rooms from LightWave');
475
+ var self = this;
476
+ var host = 'https://control-api.lightwaverf.com';
477
+ var json = request_promise_1.default.defaults({
478
+ json: true
479
+ });
480
+ var auth, token;
481
+ json.get(host + '/v1/user?password=' + pin + '&username=' + email)
482
+ .then(function (res) {
483
+ return json.get(host + '/v1/auth?application_key=' + res.application_key);
484
+ })
485
+ .then(function (res) {
486
+ token = res.token;
487
+ auth = json.defaults({
488
+ headers: {
489
+ 'X-LWRF-token': token,
490
+ 'X-LWRF-platform': 'ios',
491
+ 'X-LWRF-skin': 'lightwaverf'
492
+ }
493
+ });
494
+ return auth.get(host + '/v1/device_type?nested=1');
495
+ })
496
+ .then(function (res) {
497
+ return auth.get(host + '/v1/user_profile?nested=1');
498
+ })
499
+ .then(function (res) {
500
+ self.parseRooms(res, callback);
501
+ });
502
+ }
503
+ }
504
+ util_1.default.inherits(LightwaveRF, events_1.default.EventEmitter);
505
+ exports.default = LightwaveRF;