@evops/lightwaverf 0.0.9 → 1.0.1
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/LightwaveAccount.d.ts +11 -0
- package/.dist/LightwaveAccount.js +99 -0
- package/.dist/LightwaveAccount.test.d.ts +1 -0
- package/.dist/LightwaveAccount.test.js +26 -0
- package/.dist/LightwaveDevice.d.ts +21 -0
- package/.dist/LightwaveDevice.js +25 -0
- package/{dist → .dist}/LightwaveMessageProcessor.d.ts +0 -1
- package/{dist/LightwaveTextMessageProcessor.d.ts → .dist/LightwaveMessageProcessorForJson.d.ts} +2 -3
- package/.dist/LightwaveMessageProcessorForJson.js +24 -0
- package/{dist/LightwaveJsonMessageProcessor.d.ts → .dist/LightwaveMessageProcessorForText.d.ts} +2 -3
- package/{dist/LightwaveTextMessageProcessor.js → .dist/LightwaveMessageProcessorForText.js} +7 -6
- package/.dist/LightwaveRFClient.d.ts +35 -0
- package/.dist/LightwaveRFClient.js +217 -0
- package/.dist/LightwaveRFClient.test.d.ts +1 -0
- package/.dist/LightwaveRFClient.test.js +24 -0
- package/{dist → .dist}/Queue.d.ts +1 -1
- package/{dist → .dist}/Queue.js +3 -5
- package/.dist/index.d.ts +58 -0
- package/.dist/index.js +120 -0
- package/.dist/index.test.d.ts +1 -0
- package/.dist/index.test.js +43 -0
- package/CHANGELOG.md +69 -0
- package/README.md +453 -2
- package/package.json +13 -7
- package/vitest.config.ts +12 -0
- package/dist/LightwaveAccount.d.ts +0 -13
- package/dist/LightwaveAccount.js +0 -81
- package/dist/LightwaveDevice.d.ts +0 -26
- package/dist/LightwaveDevice.js +0 -64
- package/dist/LightwaveJsonMessageProcessor.js +0 -24
- package/dist/LightwaveMessage.d.ts +0 -9
- package/dist/LightwaveMessage.js +0 -2
- package/dist/LightwaveRFClient.d.ts +0 -32
- package/dist/LightwaveRFClient.js +0 -202
- package/dist/index.d.ts +0 -98
- package/dist/index.js +0 -278
- package/src/LightwaveAccount.ts +0 -107
- package/src/LightwaveDevice.ts +0 -66
- package/src/LightwaveJsonMessageProcessor.ts +0 -29
- package/src/LightwaveMessageProcessor.ts +0 -8
- package/src/LightwaveRFClient.ts +0 -236
- package/src/LightwaveTextMessageProcessor.ts +0 -30
- package/src/LightwaveTransaction.ts +0 -9
- package/src/Queue.ts +0 -17
- package/src/index.ts +0 -332
- /package/{dist → .dist}/LightwaveMessageProcessor.js +0 -0
- /package/{dist → .dist}/LightwaveTransaction.d.ts +0 -0
- /package/{dist → .dist}/LightwaveTransaction.js +0 -0
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import Debug, { Debugger } from "debug";
|
|
2
|
+
import { LightwaveDevice } from "./LightwaveDevice";
|
|
3
|
+
export declare class LightwaveAccount {
|
|
4
|
+
debug: Debug.Debugger;
|
|
5
|
+
email: string;
|
|
6
|
+
pin: string;
|
|
7
|
+
mainDebug: Debug.Debugger;
|
|
8
|
+
constructor(debug: Debugger, email: string, pin: string);
|
|
9
|
+
getConfiguration(callback?: (devices: LightwaveDevice[], error: any) => void): Promise<LightwaveDevice[] | undefined>;
|
|
10
|
+
parseRooms(lightwaveResponse: any, callback?: (devices: LightwaveDevice[], error: Error | null) => void): LightwaveDevice[] | undefined;
|
|
11
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.LightwaveAccount = void 0;
|
|
4
|
+
const LightwaveDevice_1 = require("./LightwaveDevice");
|
|
5
|
+
class LightwaveAccount {
|
|
6
|
+
debug;
|
|
7
|
+
email;
|
|
8
|
+
pin;
|
|
9
|
+
mainDebug;
|
|
10
|
+
constructor(debug, 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.email = email;
|
|
17
|
+
this.pin = pin;
|
|
18
|
+
}
|
|
19
|
+
async getConfiguration(callback) {
|
|
20
|
+
this.debug("Getting rooms from LightWave");
|
|
21
|
+
var self = this;
|
|
22
|
+
var host = "https://control-api.lightwaverf.com";
|
|
23
|
+
function assertResponse(response) {
|
|
24
|
+
if (!response.ok) {
|
|
25
|
+
throw new Error("Network response was not ok: " + response.statusText);
|
|
26
|
+
}
|
|
27
|
+
return response.json();
|
|
28
|
+
}
|
|
29
|
+
let token = "";
|
|
30
|
+
try {
|
|
31
|
+
this.debug("Fetching token from LightWave", {
|
|
32
|
+
url: host + "/v1/user?password=****&username=****",
|
|
33
|
+
});
|
|
34
|
+
const userResponse = await fetch(host + "/v1/user?password=" + this.pin + "&username=" + this.email);
|
|
35
|
+
this.debug("User response: %O", userResponse);
|
|
36
|
+
const userData = await assertResponse(userResponse);
|
|
37
|
+
const authResponse = await fetch(host + "/v1/auth?application_key=" + userData.application_key);
|
|
38
|
+
const authData = await assertResponse(authResponse);
|
|
39
|
+
token = authData.token;
|
|
40
|
+
const deviceTypeResponse = await fetch(host + "/v1/device_type?nested=1", {
|
|
41
|
+
headers: {
|
|
42
|
+
"X-LWRF-token": token,
|
|
43
|
+
"X-LWRF-platform": "ios",
|
|
44
|
+
"X-LWRF-skin": "lightwaverf",
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
const something = await assertResponse(deviceTypeResponse);
|
|
48
|
+
const userProfileResponse = await fetch(host + "/v1/user_profile?nested=1", {
|
|
49
|
+
headers: {
|
|
50
|
+
"X-LWRF-token": token,
|
|
51
|
+
"X-LWRF-platform": "ios",
|
|
52
|
+
"X-LWRF-skin": "lightwaverf",
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
const userProfileData = await assertResponse(userProfileResponse);
|
|
56
|
+
return self.parseRooms(userProfileData, callback);
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
this.debug("Error fetching configuration: %O", error);
|
|
60
|
+
if (!callback) {
|
|
61
|
+
throw error;
|
|
62
|
+
}
|
|
63
|
+
callback([], error);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
parseRooms(lightwaveResponse, callback) {
|
|
67
|
+
this.debug("Parsing lightwaveResponse: ", lightwaveResponse.content.estates[0].locations[0].zones[0].rooms[0]
|
|
68
|
+
.devices);
|
|
69
|
+
const home = lightwaveResponse.content.estates[0].locations[0].zones[0];
|
|
70
|
+
const devices = [];
|
|
71
|
+
for (var i = 0; i < home.rooms.length; i++) {
|
|
72
|
+
var r = home.rooms[i];
|
|
73
|
+
this.debug("Room " + r.name + " with " + r.devices.length + " devices");
|
|
74
|
+
// Get device types
|
|
75
|
+
// O: On/Off Switch
|
|
76
|
+
// D: Dimmer
|
|
77
|
+
// R: Radiator(s)
|
|
78
|
+
// P: Open/Close
|
|
79
|
+
// I: Inactive (i.e. not configured)
|
|
80
|
+
// m: Mood (inactive)
|
|
81
|
+
// M: Mood (active)
|
|
82
|
+
// o: All Off
|
|
83
|
+
var deviceTypeMapping = new Map();
|
|
84
|
+
deviceTypeMapping.set(1, LightwaveDevice_1.LightwaveDeviceType.OnOff);
|
|
85
|
+
deviceTypeMapping.set(2, LightwaveDevice_1.LightwaveDeviceType.Dimmer);
|
|
86
|
+
deviceTypeMapping.set(3, LightwaveDevice_1.LightwaveDeviceType.OnOff);
|
|
87
|
+
for (var j = 0; j < r.devices.length; j++) {
|
|
88
|
+
var d = r.devices[j];
|
|
89
|
+
const device = new LightwaveDevice_1.LightwaveDevice(this.mainDebug, r.room_number, d.device_number, r.name, d.name, deviceTypeMapping.get(d.device_type_id));
|
|
90
|
+
devices.push(device);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
if (!callback) {
|
|
94
|
+
return devices;
|
|
95
|
+
}
|
|
96
|
+
callback(devices, null);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
exports.LightwaveAccount = LightwaveAccount;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,26 @@
|
|
|
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
|
+
const debug_1 = __importDefault(require("debug"));
|
|
7
|
+
const fetch_vcr_1 = __importDefault(require("fetch-vcr"));
|
|
8
|
+
const vitest_1 = require("vitest");
|
|
9
|
+
const LightwaveAccount_1 = require("./LightwaveAccount");
|
|
10
|
+
(0, vitest_1.describe)("LightwaveAccount tests", () => {
|
|
11
|
+
fetch_vcr_1.default.configure({
|
|
12
|
+
fixturePath: __dirname + "/.fixtures",
|
|
13
|
+
mode: "playback",
|
|
14
|
+
});
|
|
15
|
+
global.fetch = fetch_vcr_1.default;
|
|
16
|
+
(0, vitest_1.beforeEach)(() => {
|
|
17
|
+
// Setup code if needed
|
|
18
|
+
process.env.DEBUG = "lightwave*";
|
|
19
|
+
debug_1.default.enable(process.env.DEBUG);
|
|
20
|
+
});
|
|
21
|
+
(0, vitest_1.it)("should parse configuration into rooms", async () => {
|
|
22
|
+
const account = new LightwaveAccount_1.LightwaveAccount((0, debug_1.default)("lightwave-rf:test"), "some@user.com", "1234");
|
|
23
|
+
const devices = await account.getConfiguration();
|
|
24
|
+
(0, vitest_1.expect)(devices?.length).toBe(20);
|
|
25
|
+
});
|
|
26
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import debug from "debug";
|
|
2
|
+
export interface ILightwaveDevice {
|
|
3
|
+
roomId: number;
|
|
4
|
+
deviceId: number;
|
|
5
|
+
roomName: string;
|
|
6
|
+
deviceName: string;
|
|
7
|
+
deviceType: string;
|
|
8
|
+
}
|
|
9
|
+
export declare enum LightwaveDeviceType {
|
|
10
|
+
Dimmer = "D",
|
|
11
|
+
OnOff = "O"
|
|
12
|
+
}
|
|
13
|
+
export declare class LightwaveDevice implements ILightwaveDevice {
|
|
14
|
+
roomId: number;
|
|
15
|
+
deviceId: number;
|
|
16
|
+
roomName: string;
|
|
17
|
+
deviceName: string;
|
|
18
|
+
deviceType: LightwaveDeviceType;
|
|
19
|
+
debug: debug.Debugger;
|
|
20
|
+
constructor(debug: debug.Debugger, roomId: number, deviceId: number, roomName: string, deviceName: string, deviceType: LightwaveDeviceType);
|
|
21
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.LightwaveDevice = exports.LightwaveDeviceType = void 0;
|
|
4
|
+
var LightwaveDeviceType;
|
|
5
|
+
(function (LightwaveDeviceType) {
|
|
6
|
+
LightwaveDeviceType["Dimmer"] = "D";
|
|
7
|
+
LightwaveDeviceType["OnOff"] = "O";
|
|
8
|
+
})(LightwaveDeviceType || (exports.LightwaveDeviceType = LightwaveDeviceType = {}));
|
|
9
|
+
class LightwaveDevice {
|
|
10
|
+
roomId;
|
|
11
|
+
deviceId;
|
|
12
|
+
roomName;
|
|
13
|
+
deviceName;
|
|
14
|
+
deviceType;
|
|
15
|
+
debug;
|
|
16
|
+
constructor(debug, roomId, deviceId, roomName, deviceName, deviceType) {
|
|
17
|
+
this.debug = debug.extend(deviceName);
|
|
18
|
+
this.roomId = roomId;
|
|
19
|
+
this.deviceId = deviceId;
|
|
20
|
+
this.roomName = roomName;
|
|
21
|
+
this.deviceName = deviceName;
|
|
22
|
+
this.deviceType = deviceType;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
exports.LightwaveDevice = LightwaveDevice;
|
package/{dist/LightwaveTextMessageProcessor.d.ts → .dist/LightwaveMessageProcessorForJson.d.ts}
RENAMED
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
/// <reference types="node" />
|
|
2
1
|
import { Debugger } from "debug";
|
|
3
|
-
import "./LightwaveTransaction";
|
|
4
2
|
import LightwaveMessageProcessor from "./LightwaveMessageProcessor";
|
|
5
|
-
|
|
3
|
+
import "./LightwaveTransaction";
|
|
4
|
+
export declare class LightwaveMessageProcessorForJson implements LightwaveMessageProcessor {
|
|
6
5
|
debug: Debugger;
|
|
7
6
|
constructor(debug: Debugger);
|
|
8
7
|
process(message: Buffer): any;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.LightwaveMessageProcessorForJson = void 0;
|
|
4
|
+
require("./LightwaveTransaction");
|
|
5
|
+
class LightwaveMessageProcessorForJson {
|
|
6
|
+
debug;
|
|
7
|
+
constructor(debug) {
|
|
8
|
+
this.debug = debug.extend("jsonMessageProcessor");
|
|
9
|
+
}
|
|
10
|
+
process(message) {
|
|
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 = json.error ?? 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.LightwaveMessageProcessorForJson = LightwaveMessageProcessorForJson;
|
package/{dist/LightwaveJsonMessageProcessor.d.ts → .dist/LightwaveMessageProcessorForText.d.ts}
RENAMED
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
/// <reference types="node" />
|
|
2
1
|
import { Debugger } from "debug";
|
|
3
|
-
import "./LightwaveTransaction";
|
|
4
2
|
import LightwaveMessageProcessor from "./LightwaveMessageProcessor";
|
|
5
|
-
|
|
3
|
+
import "./LightwaveTransaction";
|
|
4
|
+
export default class LightwaveMessageProcessorForText implements LightwaveMessageProcessor {
|
|
6
5
|
debug: Debugger;
|
|
7
6
|
constructor(debug: Debugger);
|
|
8
7
|
process(message: Buffer): any;
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
require("./LightwaveTransaction");
|
|
4
|
-
class
|
|
4
|
+
class LightwaveMessageProcessorForText {
|
|
5
|
+
debug;
|
|
5
6
|
constructor(debug) {
|
|
6
|
-
this.debug = debug.extend(
|
|
7
|
+
this.debug = debug.extend("textMessageProcessor");
|
|
7
8
|
}
|
|
8
9
|
process(message) {
|
|
9
|
-
this.debug(
|
|
10
|
-
const textMessage = message.toString(
|
|
10
|
+
this.debug("Processing message");
|
|
11
|
+
const textMessage = message.toString("utf-8");
|
|
11
12
|
var parts = textMessage.split(",");
|
|
12
13
|
var trans = parts.splice(0, 1);
|
|
13
14
|
var content = parts.join(",").replace(/(\r\n|\n|\r)/gm, "");
|
|
@@ -19,7 +20,7 @@ class LightwaveTextMessageProcessor {
|
|
|
19
20
|
}
|
|
20
21
|
canProcess(message) {
|
|
21
22
|
this.debug("Checking if can process message");
|
|
22
|
-
return message.toString(
|
|
23
|
+
return message.toString("utf-8").endsWith("\n");
|
|
23
24
|
}
|
|
24
25
|
}
|
|
25
|
-
exports.default =
|
|
26
|
+
exports.default = LightwaveMessageProcessorForText;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { Debugger } from "debug";
|
|
2
|
+
import events from "events";
|
|
3
|
+
import { LightwaveTransaction } from "./LightwaveTransaction";
|
|
4
|
+
declare interface LightwaveRFClientInterface {
|
|
5
|
+
on(event: "ready", listener: (client: LightwaveRFClient) => void): this;
|
|
6
|
+
on(event: "deviceTurnedOn", listener: (room: number, device: number) => void): this;
|
|
7
|
+
on(event: "deviceTurnedOff", listener: (room: number, device: number) => void): this;
|
|
8
|
+
on(event: "deviceDimmed", listener: (room: number, device: number, dimPercentage: number) => void): this;
|
|
9
|
+
on(event: "registered", listener: () => void): this;
|
|
10
|
+
}
|
|
11
|
+
export declare class LightwaveRFClient extends events.EventEmitter implements LightwaveRFClientInterface {
|
|
12
|
+
private ip;
|
|
13
|
+
private senderSocket;
|
|
14
|
+
private receiverSocket;
|
|
15
|
+
private commandQueue;
|
|
16
|
+
private messageProcessors;
|
|
17
|
+
private debug;
|
|
18
|
+
private transactionListeners;
|
|
19
|
+
delay: number;
|
|
20
|
+
serial?: string;
|
|
21
|
+
mac?: string;
|
|
22
|
+
model?: string;
|
|
23
|
+
uptime?: number;
|
|
24
|
+
version?: string;
|
|
25
|
+
constructor(debug: Debugger, ip?: string);
|
|
26
|
+
connect(): Promise<void>;
|
|
27
|
+
disconnect(): Promise<[void, void]>;
|
|
28
|
+
private checkRegistration;
|
|
29
|
+
send(message: string | Buffer, callback?: (transaction: LightwaveTransaction | null, error: Error) => void): void;
|
|
30
|
+
private processSendQueue;
|
|
31
|
+
private exec;
|
|
32
|
+
private processRawMessage;
|
|
33
|
+
private processLightwaveMessage;
|
|
34
|
+
}
|
|
35
|
+
export {};
|
|
@@ -0,0 +1,217 @@
|
|
|
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.LightwaveRFClient = void 0;
|
|
7
|
+
const dgram_1 = __importDefault(require("dgram"));
|
|
8
|
+
const events_1 = __importDefault(require("events"));
|
|
9
|
+
const LightwaveMessageProcessorForJson_1 = require("./LightwaveMessageProcessorForJson");
|
|
10
|
+
const LightwaveMessageProcessorForText_1 = __importDefault(require("./LightwaveMessageProcessorForText"));
|
|
11
|
+
const Queue_1 = require("./Queue");
|
|
12
|
+
const LIGHTWAVE_LINK_SEND_PORT = 9760;
|
|
13
|
+
const LIGHTWAVE_LINK_RECEIVE_PORT = 9761;
|
|
14
|
+
class LightwaveRFClient extends events_1.default.EventEmitter {
|
|
15
|
+
ip;
|
|
16
|
+
senderSocket;
|
|
17
|
+
receiverSocket;
|
|
18
|
+
commandQueue = new Queue_1.Queue();
|
|
19
|
+
messageProcessors = [];
|
|
20
|
+
debug;
|
|
21
|
+
transactionListeners = new Map();
|
|
22
|
+
delay = 125;
|
|
23
|
+
serial;
|
|
24
|
+
mac;
|
|
25
|
+
model;
|
|
26
|
+
uptime;
|
|
27
|
+
version;
|
|
28
|
+
constructor(debug, ip = "255.255.255.255") {
|
|
29
|
+
super();
|
|
30
|
+
this.ip = ip;
|
|
31
|
+
this.debug = debug.extend("client");
|
|
32
|
+
this.senderSocket = dgram_1.default.createSocket("udp4");
|
|
33
|
+
this.senderSocket.unref();
|
|
34
|
+
this.receiverSocket = dgram_1.default.createSocket("udp4");
|
|
35
|
+
this.receiverSocket.unref();
|
|
36
|
+
this.messageProcessors.push(new LightwaveMessageProcessorForJson_1.LightwaveMessageProcessorForJson(this.debug));
|
|
37
|
+
this.messageProcessors.push(new LightwaveMessageProcessorForText_1.default(this.debug));
|
|
38
|
+
}
|
|
39
|
+
async connect() {
|
|
40
|
+
const { promise, resolve, reject } = Promise.withResolvers();
|
|
41
|
+
this.senderSocket.bind();
|
|
42
|
+
this.senderSocket.once("listening", () => {
|
|
43
|
+
this.receiverSocket.bind(LIGHTWAVE_LINK_RECEIVE_PORT);
|
|
44
|
+
});
|
|
45
|
+
this.debug("Binding receiver socket on address %s and port %d", this.ip, LIGHTWAVE_LINK_RECEIVE_PORT);
|
|
46
|
+
this.receiverSocket.on("message", (buffer) => {
|
|
47
|
+
this.debug("RAW: %o", buffer.toString("utf-8"));
|
|
48
|
+
});
|
|
49
|
+
this.receiverSocket.on("message", this.processRawMessage.bind(this));
|
|
50
|
+
this.receiverSocket.once("listening", () => {
|
|
51
|
+
this.debug("Receiver socket bound", this.receiverSocket.address());
|
|
52
|
+
resolve();
|
|
53
|
+
});
|
|
54
|
+
this.receiverSocket.on("error", (err) => {
|
|
55
|
+
reject(new Error("Error binding receiver socket", {
|
|
56
|
+
cause: err,
|
|
57
|
+
}));
|
|
58
|
+
});
|
|
59
|
+
return promise;
|
|
60
|
+
}
|
|
61
|
+
async disconnect() {
|
|
62
|
+
this.receiverSocket.removeAllListeners();
|
|
63
|
+
this.senderSocket.removeAllListeners();
|
|
64
|
+
const { promise: senderPromise, resolve: senderResolve } = Promise.withResolvers();
|
|
65
|
+
const { promise: receiverPromise, resolve: receiverResolve } = Promise.withResolvers();
|
|
66
|
+
this.senderSocket.close(senderResolve);
|
|
67
|
+
this.receiverSocket.close(receiverResolve);
|
|
68
|
+
return Promise.all([senderPromise, receiverPromise]);
|
|
69
|
+
}
|
|
70
|
+
checkRegistration() {
|
|
71
|
+
this.debug("Checking registration");
|
|
72
|
+
this.send("@H", (transaction, error) => {
|
|
73
|
+
if (error) {
|
|
74
|
+
this.debug("Error: %o", error);
|
|
75
|
+
}
|
|
76
|
+
this.debug(transaction);
|
|
77
|
+
this.emit("ready", this);
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
send(message, callback) {
|
|
81
|
+
const transaction = Math.round(Math.random() * Math.pow(10, 8));
|
|
82
|
+
const messageWithTransaction = `${transaction},${message}`;
|
|
83
|
+
const transactionDebug = this.debug.extend("tx:" + transaction);
|
|
84
|
+
this.transactionListeners.set(transaction, {
|
|
85
|
+
message: message,
|
|
86
|
+
debug: transactionDebug,
|
|
87
|
+
delay: this.delay,
|
|
88
|
+
callback: callback,
|
|
89
|
+
});
|
|
90
|
+
this.debug("Queueing message: %s", messageWithTransaction);
|
|
91
|
+
this.commandQueue.add({
|
|
92
|
+
id: transaction,
|
|
93
|
+
message: messageWithTransaction,
|
|
94
|
+
debug: transactionDebug,
|
|
95
|
+
callback,
|
|
96
|
+
});
|
|
97
|
+
this.processSendQueue();
|
|
98
|
+
}
|
|
99
|
+
async processSendQueue() {
|
|
100
|
+
if (this.commandQueue.busy) {
|
|
101
|
+
setTimeout(this.processSendQueue.bind(this), this.delay);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
const command = this.commandQueue.next();
|
|
105
|
+
if (!command)
|
|
106
|
+
return;
|
|
107
|
+
this.commandQueue.busy = true;
|
|
108
|
+
const transactionListener = this.transactionListeners.get(command.id);
|
|
109
|
+
const originalCallback = transactionListener.callback;
|
|
110
|
+
transactionListener.callback = (transaction, error) => {
|
|
111
|
+
this.commandQueue.busy = false;
|
|
112
|
+
originalCallback?.(transaction, error);
|
|
113
|
+
setTimeout(this.processSendQueue.bind(this), this.delay);
|
|
114
|
+
};
|
|
115
|
+
// If we didn't hear back within the timeout
|
|
116
|
+
setTimeout(() => {
|
|
117
|
+
const listener = this.transactionListeners.get(command.id);
|
|
118
|
+
if (!listener)
|
|
119
|
+
return;
|
|
120
|
+
originalCallback?.(null, new Error("Execution expired"));
|
|
121
|
+
this.transactionListeners.delete(command.id);
|
|
122
|
+
this.processSendQueue();
|
|
123
|
+
}, 5000);
|
|
124
|
+
const transactionDebug = this.debug.extend(`transaction:${command?.id}`);
|
|
125
|
+
transactionDebug("Starting new transaction");
|
|
126
|
+
this.exec(command.message);
|
|
127
|
+
}
|
|
128
|
+
exec(message) {
|
|
129
|
+
if (message === undefined)
|
|
130
|
+
return;
|
|
131
|
+
this.debug("Sending message: %s to %s on port %d", message, this.ip, LIGHTWAVE_LINK_SEND_PORT);
|
|
132
|
+
this.senderSocket.setBroadcast(this.ip === "255.255.255.255");
|
|
133
|
+
this.senderSocket.send(message, LIGHTWAVE_LINK_SEND_PORT, this.ip, (error, bytes) => {
|
|
134
|
+
if (error) {
|
|
135
|
+
this.debug("Message send errror: %o", error);
|
|
136
|
+
}
|
|
137
|
+
this.debug("Message sent: %s, length: %d", message, bytes);
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
processRawMessage(message, remoteInfo) {
|
|
141
|
+
this.debug("Message has come through from %o", remoteInfo);
|
|
142
|
+
this.debug("Message received: %o", message.toString("ascii"));
|
|
143
|
+
for (const messageProcessor of this.messageProcessors) {
|
|
144
|
+
if (messageProcessor.canProcess(message)) {
|
|
145
|
+
this.debug("Message can be processed by %s", messageProcessor.constructor.name);
|
|
146
|
+
const lightwaveMessage = messageProcessor.process(message);
|
|
147
|
+
this.processLightwaveMessage(lightwaveMessage);
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
throw "Message cannot be processed";
|
|
152
|
+
}
|
|
153
|
+
processLightwaveMessage(lightwaveMessage) {
|
|
154
|
+
this.debug("Processing lightwave message: %o", lightwaveMessage);
|
|
155
|
+
this.debug("Current listeners: %o", this.transactionListeners);
|
|
156
|
+
this.debug("Link response fn", lightwaveMessage.fn);
|
|
157
|
+
// update info from link
|
|
158
|
+
if (lightwaveMessage.serial) {
|
|
159
|
+
this.serial ??= lightwaveMessage.serial;
|
|
160
|
+
}
|
|
161
|
+
if (lightwaveMessage.mac) {
|
|
162
|
+
this.mac ??= lightwaveMessage.mac;
|
|
163
|
+
}
|
|
164
|
+
if (lightwaveMessage.model) {
|
|
165
|
+
this.model ??= lightwaveMessage.prod;
|
|
166
|
+
}
|
|
167
|
+
if (lightwaveMessage.uptime) {
|
|
168
|
+
this.uptime = lightwaveMessage.uptime;
|
|
169
|
+
}
|
|
170
|
+
if (lightwaveMessage.ip) {
|
|
171
|
+
this.ip = lightwaveMessage.ip;
|
|
172
|
+
}
|
|
173
|
+
if (lightwaveMessage.fw) {
|
|
174
|
+
this.version ??= lightwaveMessage.fw;
|
|
175
|
+
}
|
|
176
|
+
// *!{"trans":530,"mac":"03:13:8D","time":1762607959,"pkt":"error","fn":"nonRegistered","payload":"Not yet registered. See WifiLink."}
|
|
177
|
+
if (lightwaveMessage.fn) {
|
|
178
|
+
}
|
|
179
|
+
// *!{"trans":531,"mac":"03:13:8D","time":1762607962,"type":"link","prod":"wfl","pairType":"local","msg":"success","class":"","serial":""}
|
|
180
|
+
if (lightwaveMessage.type === "link" &&
|
|
181
|
+
lightwaveMessage.msg === "success") {
|
|
182
|
+
this.emit("registered");
|
|
183
|
+
}
|
|
184
|
+
if (lightwaveMessage.fn === "on") {
|
|
185
|
+
this.emit("deviceTurnedOn", lightwaveMessage.room, lightwaveMessage.dev);
|
|
186
|
+
}
|
|
187
|
+
if (lightwaveMessage.fn === "off") {
|
|
188
|
+
this.emit("deviceTurnedOff", lightwaveMessage.room, lightwaveMessage.dev);
|
|
189
|
+
}
|
|
190
|
+
if (lightwaveMessage.fn === "dim") {
|
|
191
|
+
this.emit("deviceDimmed", lightwaveMessage.room, lightwaveMessage.dev, Math.round((lightwaveMessage.param / 32) * 100));
|
|
192
|
+
}
|
|
193
|
+
const listener = this.transactionListeners.get(lightwaveMessage.id);
|
|
194
|
+
if (!listener)
|
|
195
|
+
return;
|
|
196
|
+
if (lightwaveMessage.error?.match(/^ERR,6,/)) {
|
|
197
|
+
listener.delay = listener.delay * 2;
|
|
198
|
+
const msg = `${lightwaveMessage.id},!${listener.message}`;
|
|
199
|
+
this.debug("message errorred, retrying: %o, %o with delay: %o", msg, listener, listener.delay);
|
|
200
|
+
setTimeout(() => {
|
|
201
|
+
this.exec(msg);
|
|
202
|
+
}, Math.round(listener.delay));
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
this.commandQueue.busy = false;
|
|
206
|
+
if (listener) {
|
|
207
|
+
listener.debug("Found transaction listener");
|
|
208
|
+
listener.callback(lightwaveMessage, null);
|
|
209
|
+
listener.debug("Transaction completed, removing listener");
|
|
210
|
+
this.transactionListeners.delete(lightwaveMessage.id);
|
|
211
|
+
}
|
|
212
|
+
else {
|
|
213
|
+
this.debug("Listener not found for message: %o", lightwaveMessage);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
exports.LightwaveRFClient = LightwaveRFClient;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,24 @@
|
|
|
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
|
+
const debug_1 = __importDefault(require("debug"));
|
|
7
|
+
const vitest_1 = require("vitest");
|
|
8
|
+
const LightwaveRFClient_1 = require("./LightwaveRFClient");
|
|
9
|
+
(0, vitest_1.describe)("LightwaveRFClient tests", () => {
|
|
10
|
+
let client;
|
|
11
|
+
(0, vitest_1.beforeEach)(async () => {
|
|
12
|
+
const { promise, resolve, reject } = Promise.withResolvers();
|
|
13
|
+
const logger = (0, debug_1.default)("lightwave:test");
|
|
14
|
+
logger("Starting LightwaveRFClient test");
|
|
15
|
+
client = new LightwaveRFClient_1.LightwaveRFClient(logger);
|
|
16
|
+
await client.connect();
|
|
17
|
+
});
|
|
18
|
+
(0, vitest_1.afterEach)(async () => {
|
|
19
|
+
return client.disconnect();
|
|
20
|
+
});
|
|
21
|
+
(0, vitest_1.it)("should initialize correctly", async () => {
|
|
22
|
+
process.stderr.write(process.env.DEBUG + "\n");
|
|
23
|
+
});
|
|
24
|
+
});
|
package/{dist → .dist}/Queue.js
RENAMED
|
@@ -2,17 +2,15 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.Queue = void 0;
|
|
4
4
|
class Queue {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
this.busy = false;
|
|
8
|
-
}
|
|
5
|
+
items = [];
|
|
6
|
+
busy = false;
|
|
9
7
|
get length() {
|
|
10
8
|
return this.items.length;
|
|
11
9
|
}
|
|
12
10
|
add(item) {
|
|
13
11
|
this.items.push(item);
|
|
14
12
|
}
|
|
15
|
-
|
|
13
|
+
next() {
|
|
16
14
|
return this.items.shift();
|
|
17
15
|
}
|
|
18
16
|
}
|
package/.dist/index.d.ts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import debug from "debug";
|
|
2
|
+
import { EventEmitter } from "events";
|
|
3
|
+
import { LightwaveAccount } from "./LightwaveAccount";
|
|
4
|
+
import { ILightwaveDevice, LightwaveDevice } from "./LightwaveDevice";
|
|
5
|
+
import { LightwaveRFClient } from "./LightwaveRFClient";
|
|
6
|
+
export { LightwaveDeviceType } from "./LightwaveDevice";
|
|
7
|
+
export { type ILightwaveDevice };
|
|
8
|
+
declare class LightwaveRFConfiguration {
|
|
9
|
+
timeout?: number;
|
|
10
|
+
ip?: string;
|
|
11
|
+
file?: any;
|
|
12
|
+
host?: any;
|
|
13
|
+
email?: any;
|
|
14
|
+
pin?: any;
|
|
15
|
+
}
|
|
16
|
+
declare interface ILightwaveRF {
|
|
17
|
+
on(event: "deviceTurnedOn", listener: (roomId: number, deviceId: number) => void): this;
|
|
18
|
+
on(event: "deviceTurnedOff", listener: (roomId: number, deviceId: number) => void): this;
|
|
19
|
+
on(event: "deviceDimmed", listener: (roomId: number, deviceId: number, percentage: number) => void): this;
|
|
20
|
+
}
|
|
21
|
+
interface LightwaveEvents {
|
|
22
|
+
deviceTurnedOn: [number, number];
|
|
23
|
+
deviceTurnedOff: [number, number];
|
|
24
|
+
deviceDimmed: [number, number, number];
|
|
25
|
+
}
|
|
26
|
+
/** * LightwaveRF API
|
|
27
|
+
*
|
|
28
|
+
* @param object config The config
|
|
29
|
+
*
|
|
30
|
+
* An instance of the LightwaveRF API
|
|
31
|
+
*/
|
|
32
|
+
export default class LightwaveRF extends EventEmitter<LightwaveEvents> implements ILightwaveRF {
|
|
33
|
+
timeout: number;
|
|
34
|
+
queue: any;
|
|
35
|
+
ready: boolean;
|
|
36
|
+
awaitRegistrration: boolean;
|
|
37
|
+
currentTransactionNumber: number;
|
|
38
|
+
devices: Array<LightwaveDevice>;
|
|
39
|
+
messageCounter: number;
|
|
40
|
+
config: LightwaveRFConfiguration;
|
|
41
|
+
responseListeners: Map<number, any>;
|
|
42
|
+
lwClient: LightwaveRFClient;
|
|
43
|
+
lwAccount: LightwaveAccount;
|
|
44
|
+
debug: debug.Debugger;
|
|
45
|
+
constructor({ email, pin, ip, timeout }: LightwaveRFConfiguration);
|
|
46
|
+
get serial(): string | undefined;
|
|
47
|
+
get mac(): string | undefined;
|
|
48
|
+
get uptime(): number | undefined;
|
|
49
|
+
get model(): string | undefined;
|
|
50
|
+
get version(): string | undefined;
|
|
51
|
+
getDevices(): Promise<LightwaveDevice[]>;
|
|
52
|
+
turnOn({ roomId, deviceId, roomName, deviceName }: ILightwaveDevice): Promise<void>;
|
|
53
|
+
turnOff({ roomId, deviceId, roomName, deviceName }: ILightwaveDevice): Promise<void>;
|
|
54
|
+
dim({ roomId, deviceId, roomName, deviceName }: ILightwaveDevice, percentage: number): Promise<void>;
|
|
55
|
+
connect(): Promise<void>;
|
|
56
|
+
isRegistered(): Promise<boolean>;
|
|
57
|
+
ensureRegistration(): Promise<void>;
|
|
58
|
+
}
|