@evops/lightwaverf 0.0.4 → 0.0.8
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 +7 -0
- package/README.md +2 -82
- package/dist/LightwaveAccount.d.ts +13 -0
- package/dist/LightwaveAccount.js +81 -0
- package/dist/LightwaveDevice.d.ts +26 -0
- package/dist/LightwaveDevice.js +64 -0
- package/dist/LightwaveJsonMessageProcessor.d.ts +10 -0
- package/dist/LightwaveJsonMessageProcessor.js +24 -0
- package/dist/LightwaveMessage.d.ts +9 -0
- package/dist/LightwaveMessage.js +2 -0
- package/dist/LightwaveMessageProcessor.d.ts +7 -0
- package/dist/LightwaveMessageProcessor.js +3 -0
- package/dist/LightwaveRFClient.d.ts +32 -0
- package/dist/LightwaveRFClient.js +202 -0
- package/dist/LightwaveTextMessageProcessor.d.ts +10 -0
- package/dist/LightwaveTextMessageProcessor.js +25 -0
- package/dist/LightwaveTransaction.d.ts +8 -0
- package/dist/LightwaveTransaction.js +2 -0
- package/dist/Queue.d.ts +7 -0
- package/dist/Queue.js +19 -0
- package/dist/index.d.ts +98 -0
- package/dist/index.js +278 -0
- package/package.json +15 -11
- package/src/LightwaveAccount.ts +107 -0
- package/src/LightwaveDevice.ts +66 -0
- package/src/LightwaveJsonMessageProcessor.ts +29 -0
- package/src/LightwaveMessageProcessor.ts +8 -0
- package/src/LightwaveRFClient.ts +236 -0
- package/src/LightwaveTextMessageProcessor.ts +30 -0
- package/src/LightwaveTransaction.ts +9 -0
- package/src/Queue.ts +17 -0
- package/src/index.ts +332 -0
- package/bin/lightwaverf.js +0 -27
- package/index.js +0 -551
- package/test.js +0 -17
package/LICENSE
CHANGED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
Copyright 2022 Evops Limited
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
4
|
+
|
|
5
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
6
|
+
|
|
7
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,83 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
================
|
|
3
|
-
|
|
4
|
-
A NodeJS library for controlling devices using the LightwaveRF Wi-Fi Link
|
|
5
|
-
|
|
6
|
-
## Installation
|
|
7
|
-
|
|
8
|
-
npm install lightwaverf
|
|
9
|
-
|
|
10
|
-
## Usage
|
|
11
|
-
|
|
12
|
-
### Initialize
|
|
13
|
-
|
|
14
|
-
var LightwaveRF = require("lightwaverf");
|
|
15
|
-
var lw = new LightwaveRF({ip:"192.168.1.123",email:"name@host.com",pin:"0123"});
|
|
16
|
-
|
|
17
|
-
Or to read from a [LightwareRF Gem](https://github.com/pauly/lightwaverf) format YAML file:
|
|
18
|
-
|
|
19
|
-
var lw = new LightwaveRF({ip:"192.168.1.123", file:"/a/config/file.yml"});
|
|
20
|
-
|
|
21
|
-
### Turn a device on
|
|
22
|
-
|
|
23
|
-
To turn a device on you need to have set it up using the mobile or web app. You need the room ID and the device ID.
|
|
24
|
-
|
|
25
|
-
lw.turnDeviceOn(1 /*roomId*/, 1 /*deviceId*/, function(error, content) {
|
|
26
|
-
if (error) {
|
|
27
|
-
console.log("Error turning device on " + error.message);
|
|
28
|
-
} else {
|
|
29
|
-
console.log("Response: " + content);
|
|
30
|
-
}
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
### Turn a device off
|
|
34
|
-
|
|
35
|
-
To turn a device off you need to have set it up using the mobile or web app. You need the room ID and the device ID.
|
|
36
|
-
|
|
37
|
-
lw.turnDeviceOff(1 /*roomId*/, 1 /*deviceId*/, function(error, content) {
|
|
38
|
-
if (error) {
|
|
39
|
-
console.log("Error turning device off " + error.message);
|
|
40
|
-
} else {
|
|
41
|
-
console.log("Response: " + content);
|
|
42
|
-
}
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
### Dim the device in a room
|
|
46
|
-
|
|
47
|
-
To turn a dim the device in a room you need to have set it up using the mobile or web app. You need the room ID and device ID.
|
|
48
|
-
|
|
49
|
-
lw.setDeviceDim(1 /*roomId*/, 1 /*deviceId*/, 50 /*percentage from 0-100*/, function(error, content) {
|
|
50
|
-
if (error) {
|
|
51
|
-
console.log("Error changing dim of device " + error.message);
|
|
52
|
-
} else {
|
|
53
|
-
console.log("Response: " + content);
|
|
54
|
-
}
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
### Turn a room off
|
|
58
|
-
|
|
59
|
-
To turn a room off you need to have set it up using the mobile or web app. You need the room ID.
|
|
60
|
-
|
|
61
|
-
lw.turnRoomOff(1 /*roomId*/, function(error, content) {
|
|
62
|
-
if (error) {
|
|
63
|
-
console.log("Error turning room off " + error.message);
|
|
64
|
-
} else {
|
|
65
|
-
console.log("Response: " + content);
|
|
66
|
-
}
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
### Request the energy status
|
|
70
|
-
|
|
71
|
-
To get the current status from the LightwaveRF Energy Monitor Smart Meter you need to have linked it to the Wi-Fi Link first.
|
|
72
|
-
|
|
73
|
-
lw.requestEnergy(function(error, data) {
|
|
74
|
-
if (error) {
|
|
75
|
-
console.log("Error turning device off " + error.message);
|
|
76
|
-
} else {
|
|
77
|
-
//data: { current: 300, max: 620, today: 850, yesterday: 0 }
|
|
78
|
-
console.log("Energy ", data);
|
|
79
|
-
}
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
|
|
1
|
+
# LightwaveRF client library
|
|
83
2
|
|
|
3
|
+
Originally based on https://github.com/ollieparsley/node-lightwaverf
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import Debug, { Debugger } from "debug";
|
|
2
|
+
import { LightwaveDevice } from "./LightwaveDevice";
|
|
3
|
+
import { LightwaveRFClient } from "./LightwaveRFClient";
|
|
4
|
+
export declare class LightwaveAccount {
|
|
5
|
+
debug: Debug.Debugger;
|
|
6
|
+
client: LightwaveRFClient;
|
|
7
|
+
email: string;
|
|
8
|
+
pin: string;
|
|
9
|
+
mainDebug: Debug.Debugger;
|
|
10
|
+
constructor(debug: Debugger, client: LightwaveRFClient, email: string, pin: string);
|
|
11
|
+
getConfiguration(callback: any): void;
|
|
12
|
+
parseRooms(lightwaveResponse: any, callback: (devices: LightwaveDevice[], error: Error | null) => void): void;
|
|
13
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.LightwaveAccount = void 0;
|
|
7
|
+
const request_promise_1 = __importDefault(require("request-promise"));
|
|
8
|
+
const LightwaveDevice_1 = require("./LightwaveDevice");
|
|
9
|
+
class LightwaveAccount {
|
|
10
|
+
constructor(debug, client, email, pin) {
|
|
11
|
+
if (!email || !pin) {
|
|
12
|
+
throw "No email or pin specified. The server configuration (rooms, devices, etc.) cannot be obtained";
|
|
13
|
+
}
|
|
14
|
+
this.mainDebug = debug;
|
|
15
|
+
this.debug = debug.extend('account');
|
|
16
|
+
this.client = client;
|
|
17
|
+
this.email = email;
|
|
18
|
+
this.pin = pin;
|
|
19
|
+
}
|
|
20
|
+
getConfiguration(callback) {
|
|
21
|
+
this.debug('Getting rooms from LightWave');
|
|
22
|
+
var self = this;
|
|
23
|
+
var host = 'https://control-api.lightwaverf.com';
|
|
24
|
+
var json = request_promise_1.default.defaults({
|
|
25
|
+
json: true
|
|
26
|
+
});
|
|
27
|
+
var auth, token;
|
|
28
|
+
json.get(host + '/v1/user?password=' + this.pin + '&username=' + this.email)
|
|
29
|
+
.then(function (res) {
|
|
30
|
+
return json.get(host + '/v1/auth?application_key=' + res.application_key);
|
|
31
|
+
})
|
|
32
|
+
.then(function (res) {
|
|
33
|
+
token = res.token;
|
|
34
|
+
auth = json.defaults({
|
|
35
|
+
headers: {
|
|
36
|
+
'X-LWRF-token': token,
|
|
37
|
+
'X-LWRF-platform': 'ios',
|
|
38
|
+
'X-LWRF-skin': 'lightwaverf'
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
return auth.get(host + '/v1/device_type?nested=1');
|
|
42
|
+
})
|
|
43
|
+
.then(function (res) {
|
|
44
|
+
return auth.get(host + '/v1/user_profile?nested=1');
|
|
45
|
+
})
|
|
46
|
+
.then(function (res) {
|
|
47
|
+
self.parseRooms(res, callback);
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
parseRooms(lightwaveResponse, callback) {
|
|
51
|
+
this.debug('Parsing lightwaveResponse: ', lightwaveResponse.content.estates[0].locations[0].zones[0].rooms[0].devices);
|
|
52
|
+
const home = lightwaveResponse.content.estates[0].locations[0].zones[0];
|
|
53
|
+
const devices = [];
|
|
54
|
+
for (var i = 0; i < home.rooms.length; i++) {
|
|
55
|
+
var r = home.rooms[i];
|
|
56
|
+
this.debug("Room " + r.name + " with " + r.devices.length + " devices");
|
|
57
|
+
// Get device types
|
|
58
|
+
// O: On/Off Switch
|
|
59
|
+
// D: Dimmer
|
|
60
|
+
// R: Radiator(s)
|
|
61
|
+
// P: Open/Close
|
|
62
|
+
// I: Inactive (i.e. not configured)
|
|
63
|
+
// m: Mood (inactive)
|
|
64
|
+
// M: Mood (active)
|
|
65
|
+
// o: All Off
|
|
66
|
+
var deviceTypeMapping = new Map();
|
|
67
|
+
deviceTypeMapping.set(1, LightwaveDevice_1.LightwaveDeviceType.OnOff);
|
|
68
|
+
deviceTypeMapping.set(2, LightwaveDevice_1.LightwaveDeviceType.Dimmer);
|
|
69
|
+
deviceTypeMapping.set(3, LightwaveDevice_1.LightwaveDeviceType.OnOff);
|
|
70
|
+
for (var j = 0; j < r.devices.length; j++) {
|
|
71
|
+
var d = r.devices[j];
|
|
72
|
+
const device = new LightwaveDevice_1.LightwaveDevice(this.client, this.mainDebug, r.room_number, d.device_number, r.name, d.name, deviceTypeMapping.get(d.device_type_id));
|
|
73
|
+
devices.push(device);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
this.debug('Devices: %O', devices);
|
|
77
|
+
callback(devices, null);
|
|
78
|
+
}
|
|
79
|
+
;
|
|
80
|
+
}
|
|
81
|
+
exports.LightwaveAccount = LightwaveAccount;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import Debug from 'debug';
|
|
2
|
+
import { LightwaveRFClient } from './LightwaveRFClient';
|
|
3
|
+
export interface LightwaveRFDeviceInterface {
|
|
4
|
+
roomId: number;
|
|
5
|
+
deviceId: number;
|
|
6
|
+
roomName: string;
|
|
7
|
+
deviceName: string;
|
|
8
|
+
deviceType: string;
|
|
9
|
+
}
|
|
10
|
+
export declare enum LightwaveDeviceType {
|
|
11
|
+
Dimmer = "D",
|
|
12
|
+
OnOff = "O"
|
|
13
|
+
}
|
|
14
|
+
export declare class LightwaveDevice implements LightwaveRFDeviceInterface {
|
|
15
|
+
roomId: number;
|
|
16
|
+
deviceId: number;
|
|
17
|
+
roomName: string;
|
|
18
|
+
deviceName: string;
|
|
19
|
+
deviceType: LightwaveDeviceType;
|
|
20
|
+
client: LightwaveRFClient;
|
|
21
|
+
debug: Debug.Debugger;
|
|
22
|
+
constructor(client: LightwaveRFClient, debug: debug.Debugger, roomId: number, deviceId: number, roomName: string, deviceName: string, deviceType: LightwaveDeviceType);
|
|
23
|
+
turnOn(): Promise<void>;
|
|
24
|
+
turnOff(): Promise<void>;
|
|
25
|
+
dim(percentage: number): Promise<void>;
|
|
26
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.LightwaveDevice = exports.LightwaveDeviceType = void 0;
|
|
13
|
+
var LightwaveDeviceType;
|
|
14
|
+
(function (LightwaveDeviceType) {
|
|
15
|
+
LightwaveDeviceType["Dimmer"] = "D";
|
|
16
|
+
LightwaveDeviceType["OnOff"] = "O";
|
|
17
|
+
})(LightwaveDeviceType = exports.LightwaveDeviceType || (exports.LightwaveDeviceType = {}));
|
|
18
|
+
class LightwaveDevice {
|
|
19
|
+
constructor(client, debug, roomId, deviceId, roomName, deviceName, deviceType) {
|
|
20
|
+
this.client = client;
|
|
21
|
+
this.debug = debug.extend(deviceName);
|
|
22
|
+
this.roomId = roomId;
|
|
23
|
+
this.deviceId = deviceId;
|
|
24
|
+
this.roomName = roomName;
|
|
25
|
+
this.deviceName = deviceName;
|
|
26
|
+
this.deviceType = deviceType;
|
|
27
|
+
}
|
|
28
|
+
turnOn() {
|
|
29
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
30
|
+
return new Promise((resolve, reject) => {
|
|
31
|
+
this.debug("Device turning on");
|
|
32
|
+
this.client.send(`R${this.roomId}D${this.deviceId}F1`, (message, error) => {
|
|
33
|
+
resolve();
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
turnOff() {
|
|
39
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
40
|
+
return new Promise((resolve, reject) => {
|
|
41
|
+
this.debug("Device turning off");
|
|
42
|
+
this.client.send(`R${this.roomId}D${this.deviceId}F0`, (message, error) => {
|
|
43
|
+
if (error)
|
|
44
|
+
return reject(error);
|
|
45
|
+
resolve();
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
dim(percentage) {
|
|
51
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
52
|
+
return new Promise((resolve, reject) => {
|
|
53
|
+
this.debug("Device dimming to %d", percentage);
|
|
54
|
+
const lwDim = Math.round(percentage * 0.32);
|
|
55
|
+
this.client.send(`R${this.roomId}D${this.deviceId}FdP${lwDim}`, (message, error) => {
|
|
56
|
+
if (error)
|
|
57
|
+
return reject(error);
|
|
58
|
+
resolve();
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
exports.LightwaveDevice = LightwaveDevice;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
import { Debugger } from "debug";
|
|
3
|
+
import "./LightwaveTransaction";
|
|
4
|
+
import LightwaveMessageProcessor from "./LightwaveMessageProcessor";
|
|
5
|
+
export declare class LightwaveJsonMessageProcessor implements LightwaveMessageProcessor {
|
|
6
|
+
debug: Debugger;
|
|
7
|
+
constructor(debug: Debugger);
|
|
8
|
+
process(message: Buffer): any;
|
|
9
|
+
canProcess(message: Buffer): boolean;
|
|
10
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.LightwaveJsonMessageProcessor = void 0;
|
|
4
|
+
require("./LightwaveTransaction");
|
|
5
|
+
class LightwaveJsonMessageProcessor {
|
|
6
|
+
constructor(debug) {
|
|
7
|
+
this.debug = debug.extend('jsonMessageProcessor');
|
|
8
|
+
}
|
|
9
|
+
process(message) {
|
|
10
|
+
var _a;
|
|
11
|
+
this.debug('Processing message');
|
|
12
|
+
const textMessage = message.toString('utf-8').replace('*!', '');
|
|
13
|
+
const json = JSON.parse(textMessage);
|
|
14
|
+
const response = json;
|
|
15
|
+
response.id = json.trans;
|
|
16
|
+
response.error = (_a = json.error) !== null && _a !== void 0 ? _a : null;
|
|
17
|
+
return response;
|
|
18
|
+
}
|
|
19
|
+
canProcess(message) {
|
|
20
|
+
this.debug("Checking if can process message");
|
|
21
|
+
return message.toString('utf-8').startsWith('*!');
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
exports.LightwaveJsonMessageProcessor = LightwaveJsonMessageProcessor;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
import events from 'events';
|
|
3
|
+
import { Debugger } from 'debug';
|
|
4
|
+
import { LightwaveTransaction } from './LightwaveTransaction';
|
|
5
|
+
declare interface LightwaveRFClientInterface {
|
|
6
|
+
on(event: 'ready', listener: (client: LightwaveRFClient) => void): this;
|
|
7
|
+
on(event: 'deviceTurnedOn', listener: (room: number, device: number) => void): this;
|
|
8
|
+
on(event: 'deviceTurnedOff', listener: (room: number, device: number) => void): this;
|
|
9
|
+
on(event: 'deviceDimmed', listener: (room: number, device: number, dimPercentage: number) => void): this;
|
|
10
|
+
}
|
|
11
|
+
export declare class LightwaveRFClient extends events.EventEmitter implements LightwaveRFClientInterface {
|
|
12
|
+
private senderSocket;
|
|
13
|
+
private receiverSocket;
|
|
14
|
+
private senderSocketReady;
|
|
15
|
+
private receiverSocketReady;
|
|
16
|
+
private commandQueue;
|
|
17
|
+
private messageProcessors;
|
|
18
|
+
private debug;
|
|
19
|
+
private ip;
|
|
20
|
+
private transactionListeners;
|
|
21
|
+
delay: number;
|
|
22
|
+
constructor(debug: Debugger, ip?: string, port?: number);
|
|
23
|
+
stop(): void;
|
|
24
|
+
private socketReady;
|
|
25
|
+
private checkRegistration;
|
|
26
|
+
send(message: (string | Buffer), callback: (transaction: LightwaveTransaction | null, error: Error) => void): void;
|
|
27
|
+
private processQueue;
|
|
28
|
+
private exec;
|
|
29
|
+
private processRawMessage;
|
|
30
|
+
processLightwaveMessage(lightwaveMessage: any): void;
|
|
31
|
+
}
|
|
32
|
+
export {};
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.LightwaveRFClient = void 0;
|
|
16
|
+
const dgram_1 = __importDefault(require("dgram"));
|
|
17
|
+
const events_1 = __importDefault(require("events"));
|
|
18
|
+
const LightwaveTextMessageProcessor_1 = __importDefault(require("./LightwaveTextMessageProcessor"));
|
|
19
|
+
const LightwaveJsonMessageProcessor_1 = require("./LightwaveJsonMessageProcessor");
|
|
20
|
+
const Queue_1 = require("./Queue");
|
|
21
|
+
class LightwaveRFClient extends events_1.default.EventEmitter {
|
|
22
|
+
constructor(debug, ip = "255.255.255.255", port = 9761) {
|
|
23
|
+
super();
|
|
24
|
+
this.senderSocketReady = false;
|
|
25
|
+
this.receiverSocketReady = false;
|
|
26
|
+
this.commandQueue = new Queue_1.Queue();
|
|
27
|
+
this.messageProcessors = [];
|
|
28
|
+
this.transactionListeners = new Map();
|
|
29
|
+
this.delay = 125;
|
|
30
|
+
this.ip = ip;
|
|
31
|
+
this.debug = debug.extend('client');
|
|
32
|
+
this.senderSocket = dgram_1.default.createSocket('udp4');
|
|
33
|
+
this.receiverSocket = dgram_1.default.createSocket('udp4');
|
|
34
|
+
this.messageProcessors.push(new LightwaveJsonMessageProcessor_1.LightwaveJsonMessageProcessor(this.debug));
|
|
35
|
+
this.messageProcessors.push(new LightwaveTextMessageProcessor_1.default(this.debug));
|
|
36
|
+
this.receiverSocket.on('message', (buffer) => {
|
|
37
|
+
this.debug("RAW: %o", buffer.toString('utf-8'));
|
|
38
|
+
});
|
|
39
|
+
this.receiverSocket.on('message', this.processRawMessage.bind(this));
|
|
40
|
+
this.receiverSocket.once('listening', () => {
|
|
41
|
+
this.debug("Receiver socket bound", this.receiverSocket.address());
|
|
42
|
+
this.receiverSocketReady = true;
|
|
43
|
+
});
|
|
44
|
+
this.receiverSocket.once('listening', this.socketReady.bind(this));
|
|
45
|
+
this.senderSocket.once('listening', () => {
|
|
46
|
+
this.debug("Sender socket bound", this.senderSocket.address());
|
|
47
|
+
this.senderSocketReady = true;
|
|
48
|
+
});
|
|
49
|
+
this.senderSocket.once('listening', this.socketReady.bind(this));
|
|
50
|
+
this.senderSocket.unref();
|
|
51
|
+
this.senderSocket.bind();
|
|
52
|
+
this.debug("Binding receiver socket on address %s and port %d", ip, port);
|
|
53
|
+
this.receiverSocket.bind(port);
|
|
54
|
+
this.receiverSocket.on('error', () => {
|
|
55
|
+
throw new Error("Error binding receiver socket");
|
|
56
|
+
});
|
|
57
|
+
process.on('SIGINT', () => {
|
|
58
|
+
this.debug("\nClosing sockets");
|
|
59
|
+
this.stop();
|
|
60
|
+
process.exit();
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
stop() {
|
|
64
|
+
try {
|
|
65
|
+
this.receiverSocket.close();
|
|
66
|
+
}
|
|
67
|
+
catch (_a) { }
|
|
68
|
+
}
|
|
69
|
+
socketReady() {
|
|
70
|
+
if (!this.senderSocketReady)
|
|
71
|
+
return;
|
|
72
|
+
if (!this.receiverSocketReady)
|
|
73
|
+
return;
|
|
74
|
+
this.checkRegistration();
|
|
75
|
+
this.processQueue();
|
|
76
|
+
}
|
|
77
|
+
checkRegistration() {
|
|
78
|
+
this.debug("Checking registration");
|
|
79
|
+
this.send("@H", (transaction, error) => {
|
|
80
|
+
if (error) {
|
|
81
|
+
this.debug('Error: %o', error);
|
|
82
|
+
}
|
|
83
|
+
this.debug(transaction);
|
|
84
|
+
this.emit('ready');
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
send(message, callback) {
|
|
88
|
+
const transaction = Math.round(Math.random() * Math.pow(10, 8));
|
|
89
|
+
const messageWithTransaction = `${transaction},!${message}`;
|
|
90
|
+
const transactionDebug = this.debug.extend("transaction:" + transaction);
|
|
91
|
+
this.transactionListeners.set(transaction, {
|
|
92
|
+
message: message,
|
|
93
|
+
debug: transactionDebug,
|
|
94
|
+
delay: this.delay,
|
|
95
|
+
callback: callback
|
|
96
|
+
});
|
|
97
|
+
this.debug("Queueing message: %s", messageWithTransaction);
|
|
98
|
+
this.commandQueue.add({
|
|
99
|
+
id: transaction,
|
|
100
|
+
message: messageWithTransaction,
|
|
101
|
+
debug: transactionDebug,
|
|
102
|
+
callback
|
|
103
|
+
});
|
|
104
|
+
this.processQueue();
|
|
105
|
+
}
|
|
106
|
+
processQueue() {
|
|
107
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
108
|
+
if (this.commandQueue.busy) {
|
|
109
|
+
setTimeout(this.processQueue.bind(this), this.delay);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
;
|
|
113
|
+
const command = this.commandQueue.receive();
|
|
114
|
+
if (!command)
|
|
115
|
+
return;
|
|
116
|
+
this.commandQueue.busy = true;
|
|
117
|
+
const transactionListener = this.transactionListeners.get(command.id);
|
|
118
|
+
const originalCallback = transactionListener.callback;
|
|
119
|
+
transactionListener.callback = (transaction, error) => {
|
|
120
|
+
this.commandQueue.busy = false;
|
|
121
|
+
setTimeout(this.processQueue.bind(this), this.delay);
|
|
122
|
+
originalCallback(transaction, error);
|
|
123
|
+
};
|
|
124
|
+
setTimeout(() => {
|
|
125
|
+
const listener = this.transactionListeners.get(command.id);
|
|
126
|
+
if (!listener)
|
|
127
|
+
return;
|
|
128
|
+
originalCallback(null, new Error("Execution expired"));
|
|
129
|
+
this.transactionListeners.delete(command.id);
|
|
130
|
+
this.processQueue();
|
|
131
|
+
}, 5000);
|
|
132
|
+
const transactionDebug = this.debug.extend(`transaction:${command === null || command === void 0 ? void 0 : command.id}`);
|
|
133
|
+
transactionDebug("Starting new transaction");
|
|
134
|
+
this.exec(command.message, (message, error) => { });
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
exec(message, callback) {
|
|
138
|
+
if (message === undefined)
|
|
139
|
+
return;
|
|
140
|
+
this.debug("Sending message: %s to %s on port %d", message, this.ip, 9760);
|
|
141
|
+
this.senderSocket.setBroadcast(this.ip === "255.255.255.255");
|
|
142
|
+
this.senderSocket.send(message, 9760, this.ip, (error, bytes) => {
|
|
143
|
+
if (error) {
|
|
144
|
+
this.debug("Message send errror: %o", error);
|
|
145
|
+
}
|
|
146
|
+
this.debug("Message sent: %s, length: %d", message, bytes);
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
processRawMessage(message, remoteInfo) {
|
|
150
|
+
this.debug("Message has come through from %o", remoteInfo);
|
|
151
|
+
this.debug("Message received: %o", message.toString('ascii'));
|
|
152
|
+
for (const messageProcessor of this.messageProcessors) {
|
|
153
|
+
if (messageProcessor.canProcess(message)) {
|
|
154
|
+
this.debug("Message can be processed by %s", messageProcessor.constructor.name);
|
|
155
|
+
const lightwaveMessage = messageProcessor.process(message);
|
|
156
|
+
this.processLightwaveMessage(lightwaveMessage);
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
throw "Message cannot be processed";
|
|
161
|
+
}
|
|
162
|
+
processLightwaveMessage(lightwaveMessage) {
|
|
163
|
+
var _a;
|
|
164
|
+
this.debug("Processing lightwave message: %o", lightwaveMessage);
|
|
165
|
+
this.debug("Current listeners: %o", this.transactionListeners);
|
|
166
|
+
this.debug("Link response fn", lightwaveMessage.fn);
|
|
167
|
+
if (lightwaveMessage.fn === "on") {
|
|
168
|
+
this.emit("deviceTurnedOn", lightwaveMessage.room, lightwaveMessage.dev);
|
|
169
|
+
}
|
|
170
|
+
if (lightwaveMessage.fn === "off") {
|
|
171
|
+
this.emit("deviceTurnedOff", lightwaveMessage.room, lightwaveMessage.dev);
|
|
172
|
+
}
|
|
173
|
+
if (lightwaveMessage.fn === "dim") {
|
|
174
|
+
this.emit("deviceDimmed", lightwaveMessage.room, lightwaveMessage.dev, Math.round(lightwaveMessage.param / 32 * 100));
|
|
175
|
+
}
|
|
176
|
+
const listener = this.transactionListeners.get(lightwaveMessage.id);
|
|
177
|
+
if (!listener)
|
|
178
|
+
return;
|
|
179
|
+
if ((_a = lightwaveMessage.error) === null || _a === void 0 ? void 0 : _a.match(/^ERR,6,/)) {
|
|
180
|
+
listener.delay = listener.delay * 2;
|
|
181
|
+
const msg = `${lightwaveMessage.id},!${listener.message}`;
|
|
182
|
+
this.debug("message errorred, retrying: %o, %o with delay: %o", msg, listener, listener.delay);
|
|
183
|
+
setTimeout(() => {
|
|
184
|
+
this.exec(msg, (message, error) => {
|
|
185
|
+
this.debug("message: %o, error: %o", message, error);
|
|
186
|
+
});
|
|
187
|
+
}, Math.round(listener.delay));
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
if (listener) {
|
|
191
|
+
listener.debug("Found transaction listener");
|
|
192
|
+
listener.callback(lightwaveMessage, null);
|
|
193
|
+
listener.debug("Transaction completed, removing listener");
|
|
194
|
+
this.transactionListeners.delete(lightwaveMessage.id);
|
|
195
|
+
this.commandQueue.busy = false;
|
|
196
|
+
}
|
|
197
|
+
else {
|
|
198
|
+
this.debug("Listener not found for message: %o", lightwaveMessage);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
exports.LightwaveRFClient = LightwaveRFClient;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
import { Debugger } from "debug";
|
|
3
|
+
import "./LightwaveTransaction";
|
|
4
|
+
import LightwaveMessageProcessor from "./LightwaveMessageProcessor";
|
|
5
|
+
export default class LightwaveTextMessageProcessor implements LightwaveMessageProcessor {
|
|
6
|
+
debug: Debugger;
|
|
7
|
+
constructor(debug: Debugger);
|
|
8
|
+
process(message: Buffer): any;
|
|
9
|
+
canProcess(message: Buffer): boolean;
|
|
10
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
require("./LightwaveTransaction");
|
|
4
|
+
class LightwaveTextMessageProcessor {
|
|
5
|
+
constructor(debug) {
|
|
6
|
+
this.debug = debug.extend('textMessageProcessor');
|
|
7
|
+
}
|
|
8
|
+
process(message) {
|
|
9
|
+
this.debug('Processing message');
|
|
10
|
+
const textMessage = message.toString('utf-8');
|
|
11
|
+
var parts = textMessage.split(",");
|
|
12
|
+
var trans = parts.splice(0, 1);
|
|
13
|
+
var content = parts.join(",").replace(/(\r\n|\n|\r)/gm, "");
|
|
14
|
+
const response = {};
|
|
15
|
+
response.id = parseInt(trans[0]);
|
|
16
|
+
response.message = content;
|
|
17
|
+
response.error = content.match("^ERR") ? content : null;
|
|
18
|
+
return response;
|
|
19
|
+
}
|
|
20
|
+
canProcess(message) {
|
|
21
|
+
this.debug("Checking if can process message");
|
|
22
|
+
return message.toString('utf-8').endsWith("\n");
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
exports.default = LightwaveTextMessageProcessor;
|
package/dist/Queue.d.ts
ADDED
package/dist/Queue.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Queue = void 0;
|
|
4
|
+
class Queue {
|
|
5
|
+
constructor() {
|
|
6
|
+
this.items = [];
|
|
7
|
+
this.busy = false;
|
|
8
|
+
}
|
|
9
|
+
get length() {
|
|
10
|
+
return this.items.length;
|
|
11
|
+
}
|
|
12
|
+
add(item) {
|
|
13
|
+
this.items.push(item);
|
|
14
|
+
}
|
|
15
|
+
receive() {
|
|
16
|
+
return this.items.shift();
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
exports.Queue = Queue;
|