@evops/lightwaverf 1.0.3 → 1.0.5
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/LightwaveCommandQueue.d.ts +39 -0
- package/.dist/LightwaveCommandQueue.js +146 -0
- package/.dist/LightwaveRFClient.d.ts +6 -5
- package/.dist/LightwaveRFClient.js +32 -82
- package/.dist/index.d.ts +4 -1
- package/.dist/index.js +30 -19
- package/.dist/index.test.js +23 -8
- package/package.json +1 -1
- package/vitest.config.ts +1 -1
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { Debugger } from "debug";
|
|
2
|
+
export interface QueuedTransaction {
|
|
3
|
+
id: number;
|
|
4
|
+
originalMessage: string;
|
|
5
|
+
message: string;
|
|
6
|
+
resolve: (value: TransactionResponse) => void;
|
|
7
|
+
reject: (error: Error) => void;
|
|
8
|
+
timeoutId?: ReturnType<typeof setTimeout>;
|
|
9
|
+
retryTimeoutId?: ReturnType<typeof setTimeout>;
|
|
10
|
+
delay: number;
|
|
11
|
+
completed: boolean;
|
|
12
|
+
}
|
|
13
|
+
export interface TransactionResponse {
|
|
14
|
+
id: number;
|
|
15
|
+
[key: string]: unknown;
|
|
16
|
+
}
|
|
17
|
+
export interface LightwaveCommandQueueOptions {
|
|
18
|
+
debug: Debugger;
|
|
19
|
+
delay?: number;
|
|
20
|
+
timeout?: number;
|
|
21
|
+
onExecute: (message: string) => void;
|
|
22
|
+
}
|
|
23
|
+
export declare class LightwaveCommandQueue {
|
|
24
|
+
private queue;
|
|
25
|
+
private transactions;
|
|
26
|
+
private busy;
|
|
27
|
+
private debug;
|
|
28
|
+
private delay;
|
|
29
|
+
private timeout;
|
|
30
|
+
private onExecute;
|
|
31
|
+
constructor(options: LightwaveCommandQueueOptions);
|
|
32
|
+
send(message: string | Buffer): Promise<TransactionResponse>;
|
|
33
|
+
handleResponse(response: TransactionResponse): void;
|
|
34
|
+
handleRetryableError(transactionId: number): void;
|
|
35
|
+
destroy(): void;
|
|
36
|
+
private processQueue;
|
|
37
|
+
private completeTransaction;
|
|
38
|
+
private failTransaction;
|
|
39
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.LightwaveCommandQueue = void 0;
|
|
4
|
+
class LightwaveCommandQueue {
|
|
5
|
+
queue = [];
|
|
6
|
+
transactions = new Map();
|
|
7
|
+
busy = false;
|
|
8
|
+
debug;
|
|
9
|
+
delay;
|
|
10
|
+
timeout;
|
|
11
|
+
onExecute;
|
|
12
|
+
constructor(options) {
|
|
13
|
+
this.debug = options.debug.extend("queue");
|
|
14
|
+
this.delay = options.delay ?? 800;
|
|
15
|
+
this.timeout = options.timeout ?? 10000;
|
|
16
|
+
this.onExecute = options.onExecute;
|
|
17
|
+
}
|
|
18
|
+
send(message) {
|
|
19
|
+
const transactionId = Math.round(Math.random() * Math.pow(10, 8));
|
|
20
|
+
const messageStr = typeof message === "string" ? message : message.toString("utf-8");
|
|
21
|
+
const messageWithTransaction = `${transactionId},${messageStr}`;
|
|
22
|
+
this.debug("[%d] Queueing message: %s", Date.now(), messageWithTransaction);
|
|
23
|
+
const { promise, resolve, reject } = Promise.withResolvers();
|
|
24
|
+
const transaction = {
|
|
25
|
+
id: transactionId,
|
|
26
|
+
originalMessage: messageStr,
|
|
27
|
+
message: messageWithTransaction,
|
|
28
|
+
resolve,
|
|
29
|
+
reject,
|
|
30
|
+
delay: this.delay,
|
|
31
|
+
completed: false,
|
|
32
|
+
};
|
|
33
|
+
this.queue.push(transaction);
|
|
34
|
+
this.transactions.set(transactionId, transaction);
|
|
35
|
+
this.processQueue();
|
|
36
|
+
return promise;
|
|
37
|
+
}
|
|
38
|
+
handleResponse(response) {
|
|
39
|
+
const transaction = this.transactions.get(response.id);
|
|
40
|
+
if (!transaction || transaction.completed) {
|
|
41
|
+
this.debug("No pending transaction found for id %d or already completed", response.id);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
this.debug("[%d] Transaction %d completed", Date.now(), response.id);
|
|
45
|
+
this.completeTransaction(transaction, response);
|
|
46
|
+
}
|
|
47
|
+
handleRetryableError(transactionId) {
|
|
48
|
+
const transaction = this.transactions.get(transactionId);
|
|
49
|
+
if (!transaction || transaction.completed) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
// Clear existing timeout
|
|
53
|
+
if (transaction.timeoutId) {
|
|
54
|
+
clearTimeout(transaction.timeoutId);
|
|
55
|
+
transaction.timeoutId = undefined;
|
|
56
|
+
}
|
|
57
|
+
// Exponential backoff, max 10 seconds
|
|
58
|
+
transaction.delay = Math.min(transaction.delay * 2, 10000);
|
|
59
|
+
const retryMessage = `${transactionId},${transaction.originalMessage}`;
|
|
60
|
+
this.debug("Message errored, retrying: %s with delay: %d", retryMessage, transaction.delay);
|
|
61
|
+
// Set timeout for retry
|
|
62
|
+
transaction.timeoutId = setTimeout(() => {
|
|
63
|
+
if (transaction.completed)
|
|
64
|
+
return;
|
|
65
|
+
this.debug("Retry timeout expired for transaction %d", transactionId);
|
|
66
|
+
this.failTransaction(transaction, new Error(`Retry timeout expired ${transactionId}`));
|
|
67
|
+
}, 5000 + transaction.delay * 2);
|
|
68
|
+
// Schedule retry
|
|
69
|
+
transaction.retryTimeoutId = setTimeout(() => {
|
|
70
|
+
if (transaction.completed)
|
|
71
|
+
return;
|
|
72
|
+
this.onExecute(retryMessage);
|
|
73
|
+
}, Math.round(transaction.delay * 2));
|
|
74
|
+
}
|
|
75
|
+
destroy() {
|
|
76
|
+
for (const transaction of this.transactions.values()) {
|
|
77
|
+
if (transaction.timeoutId) {
|
|
78
|
+
clearTimeout(transaction.timeoutId);
|
|
79
|
+
}
|
|
80
|
+
if (transaction.retryTimeoutId) {
|
|
81
|
+
clearTimeout(transaction.retryTimeoutId);
|
|
82
|
+
}
|
|
83
|
+
if (!transaction.completed) {
|
|
84
|
+
transaction.reject(new Error("Queue destroyed"));
|
|
85
|
+
transaction.completed = true;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
this.transactions.clear();
|
|
89
|
+
this.queue = [];
|
|
90
|
+
this.busy = false;
|
|
91
|
+
}
|
|
92
|
+
processQueue() {
|
|
93
|
+
if (this.busy) {
|
|
94
|
+
this.debug("Queue is busy, waiting to process");
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
const transaction = this.queue.shift();
|
|
98
|
+
if (!transaction) {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
this.debug("Processing transaction: %d", transaction.id);
|
|
102
|
+
this.busy = true;
|
|
103
|
+
// Set timeout for response
|
|
104
|
+
transaction.timeoutId = setTimeout(() => {
|
|
105
|
+
if (transaction.completed)
|
|
106
|
+
return;
|
|
107
|
+
this.debug("Transaction %d timed out", transaction.id);
|
|
108
|
+
this.failTransaction(transaction, new Error(`Execution expired ${transaction.id}`));
|
|
109
|
+
}, this.timeout);
|
|
110
|
+
this.onExecute(transaction.message);
|
|
111
|
+
}
|
|
112
|
+
completeTransaction(transaction, response) {
|
|
113
|
+
if (transaction.completed)
|
|
114
|
+
return;
|
|
115
|
+
transaction.completed = true;
|
|
116
|
+
if (transaction.timeoutId) {
|
|
117
|
+
clearTimeout(transaction.timeoutId);
|
|
118
|
+
}
|
|
119
|
+
if (transaction.retryTimeoutId) {
|
|
120
|
+
clearTimeout(transaction.retryTimeoutId);
|
|
121
|
+
}
|
|
122
|
+
this.transactions.delete(transaction.id);
|
|
123
|
+
transaction.resolve(response);
|
|
124
|
+
this.debug("Transaction %d completed, scheduling next in %dms", transaction.id, this.delay);
|
|
125
|
+
setTimeout(() => {
|
|
126
|
+
this.busy = false;
|
|
127
|
+
this.processQueue();
|
|
128
|
+
}, this.delay);
|
|
129
|
+
}
|
|
130
|
+
failTransaction(transaction, error) {
|
|
131
|
+
if (transaction.completed)
|
|
132
|
+
return;
|
|
133
|
+
transaction.completed = true;
|
|
134
|
+
if (transaction.timeoutId) {
|
|
135
|
+
clearTimeout(transaction.timeoutId);
|
|
136
|
+
}
|
|
137
|
+
if (transaction.retryTimeoutId) {
|
|
138
|
+
clearTimeout(transaction.retryTimeoutId);
|
|
139
|
+
}
|
|
140
|
+
this.transactions.delete(transaction.id);
|
|
141
|
+
transaction.reject(error);
|
|
142
|
+
this.busy = false;
|
|
143
|
+
this.processQueue();
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
exports.LightwaveCommandQueue = LightwaveCommandQueue;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Debugger } from "debug";
|
|
2
2
|
import events from "events";
|
|
3
|
-
import {
|
|
3
|
+
import { TransactionResponse } from "./LightwaveCommandQueue";
|
|
4
4
|
declare interface LightwaveRFClientInterface {
|
|
5
5
|
on(event: "ready", listener: (client: LightwaveRFClient) => void): this;
|
|
6
6
|
on(event: "deviceTurnedOn", listener: (room: number, device: number) => void): this;
|
|
@@ -15,19 +15,20 @@ export declare class LightwaveRFClient extends events.EventEmitter implements Li
|
|
|
15
15
|
private commandQueue;
|
|
16
16
|
private messageProcessors;
|
|
17
17
|
private debug;
|
|
18
|
-
private transactionListeners;
|
|
19
18
|
delay: number;
|
|
20
19
|
serial?: string;
|
|
21
20
|
mac?: string;
|
|
22
21
|
model?: string;
|
|
23
22
|
uptime?: number;
|
|
24
23
|
version?: string;
|
|
25
|
-
|
|
24
|
+
discoverLinkIp: boolean;
|
|
25
|
+
constructor(debug: Debugger, ip?: string, { discoverLinkIp }?: {
|
|
26
|
+
discoverLinkIp?: boolean;
|
|
27
|
+
});
|
|
26
28
|
connect(): Promise<void>;
|
|
27
29
|
disconnect(): Promise<[void, void]>;
|
|
28
30
|
private checkRegistration;
|
|
29
|
-
send(message: string | Buffer
|
|
30
|
-
private processSendQueue;
|
|
31
|
+
send(message: string | Buffer): Promise<TransactionResponse>;
|
|
31
32
|
private exec;
|
|
32
33
|
private processRawMessage;
|
|
33
34
|
private processLightwaveMessage;
|
|
@@ -6,33 +6,39 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.LightwaveRFClient = void 0;
|
|
7
7
|
const dgram_1 = __importDefault(require("dgram"));
|
|
8
8
|
const events_1 = __importDefault(require("events"));
|
|
9
|
+
const LightwaveCommandQueue_1 = require("./LightwaveCommandQueue");
|
|
9
10
|
const LightwaveMessageProcessorForJson_1 = require("./LightwaveMessageProcessorForJson");
|
|
10
11
|
const LightwaveMessageProcessorForText_1 = __importDefault(require("./LightwaveMessageProcessorForText"));
|
|
11
|
-
const Queue_1 = require("./Queue");
|
|
12
12
|
const LIGHTWAVE_LINK_SEND_PORT = 9760;
|
|
13
13
|
const LIGHTWAVE_LINK_RECEIVE_PORT = 9761;
|
|
14
14
|
class LightwaveRFClient extends events_1.default.EventEmitter {
|
|
15
15
|
ip;
|
|
16
16
|
senderSocket;
|
|
17
17
|
receiverSocket;
|
|
18
|
-
commandQueue
|
|
18
|
+
commandQueue;
|
|
19
19
|
messageProcessors = [];
|
|
20
20
|
debug;
|
|
21
|
-
|
|
22
|
-
delay = 125;
|
|
21
|
+
delay = 800;
|
|
23
22
|
serial;
|
|
24
23
|
mac;
|
|
25
24
|
model;
|
|
26
25
|
uptime;
|
|
27
26
|
version;
|
|
28
|
-
|
|
27
|
+
discoverLinkIp;
|
|
28
|
+
constructor(debug, ip = "255.255.255.255", { discoverLinkIp = true } = {}) {
|
|
29
29
|
super();
|
|
30
30
|
this.ip = ip;
|
|
31
|
+
this.discoverLinkIp = discoverLinkIp;
|
|
31
32
|
this.debug = debug.extend("client");
|
|
32
33
|
this.senderSocket = dgram_1.default.createSocket("udp4");
|
|
33
34
|
this.senderSocket.unref();
|
|
34
35
|
this.receiverSocket = dgram_1.default.createSocket("udp4");
|
|
35
36
|
this.receiverSocket.unref();
|
|
37
|
+
this.commandQueue = new LightwaveCommandQueue_1.LightwaveCommandQueue({
|
|
38
|
+
debug: this.debug,
|
|
39
|
+
delay: this.delay,
|
|
40
|
+
onExecute: (message) => this.exec(message),
|
|
41
|
+
});
|
|
36
42
|
this.messageProcessors.push(new LightwaveMessageProcessorForJson_1.LightwaveMessageProcessorForJson(this.debug));
|
|
37
43
|
this.messageProcessors.push(new LightwaveMessageProcessorForText_1.default(this.debug));
|
|
38
44
|
}
|
|
@@ -61,69 +67,26 @@ class LightwaveRFClient extends events_1.default.EventEmitter {
|
|
|
61
67
|
async disconnect() {
|
|
62
68
|
this.receiverSocket.removeAllListeners();
|
|
63
69
|
this.senderSocket.removeAllListeners();
|
|
70
|
+
this.commandQueue.destroy();
|
|
64
71
|
const { promise: senderPromise, resolve: senderResolve } = Promise.withResolvers();
|
|
65
72
|
const { promise: receiverPromise, resolve: receiverResolve } = Promise.withResolvers();
|
|
66
73
|
this.senderSocket.close(senderResolve);
|
|
67
74
|
this.receiverSocket.close(receiverResolve);
|
|
68
75
|
return Promise.all([senderPromise, receiverPromise]);
|
|
69
76
|
}
|
|
70
|
-
checkRegistration() {
|
|
77
|
+
async checkRegistration() {
|
|
71
78
|
this.debug("Checking registration");
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
this.debug("Error: %o", error);
|
|
75
|
-
}
|
|
79
|
+
try {
|
|
80
|
+
const transaction = await this.send("@H");
|
|
76
81
|
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
82
|
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
this.
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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);
|
|
83
|
+
catch (error) {
|
|
84
|
+
this.debug("Error: %o", error);
|
|
85
|
+
}
|
|
86
|
+
this.emit("ready", this);
|
|
87
|
+
}
|
|
88
|
+
send(message) {
|
|
89
|
+
return this.commandQueue.send(message);
|
|
127
90
|
}
|
|
128
91
|
exec(message) {
|
|
129
92
|
if (message === undefined)
|
|
@@ -144,15 +107,14 @@ class LightwaveRFClient extends events_1.default.EventEmitter {
|
|
|
144
107
|
if (messageProcessor.canProcess(message)) {
|
|
145
108
|
this.debug("Message can be processed by %s", messageProcessor.constructor.name);
|
|
146
109
|
const lightwaveMessage = messageProcessor.process(message);
|
|
147
|
-
this.processLightwaveMessage(lightwaveMessage);
|
|
110
|
+
this.processLightwaveMessage(lightwaveMessage, remoteInfo);
|
|
148
111
|
return;
|
|
149
112
|
}
|
|
150
113
|
}
|
|
151
114
|
throw "Message cannot be processed";
|
|
152
115
|
}
|
|
153
|
-
processLightwaveMessage(lightwaveMessage) {
|
|
116
|
+
processLightwaveMessage(lightwaveMessage, remoteInfo) {
|
|
154
117
|
this.debug("Processing lightwave message: %o", lightwaveMessage);
|
|
155
|
-
this.debug("Current listeners: %o", this.transactionListeners);
|
|
156
118
|
this.debug("Link response fn", lightwaveMessage.fn);
|
|
157
119
|
// update info from link
|
|
158
120
|
if (lightwaveMessage.serial) {
|
|
@@ -167,8 +129,9 @@ class LightwaveRFClient extends events_1.default.EventEmitter {
|
|
|
167
129
|
if (lightwaveMessage.uptime) {
|
|
168
130
|
this.uptime = lightwaveMessage.uptime;
|
|
169
131
|
}
|
|
170
|
-
if (
|
|
171
|
-
this.ip
|
|
132
|
+
if (this.discoverLinkIp) {
|
|
133
|
+
this.debug(`Updating link ip to ${remoteInfo.address}`);
|
|
134
|
+
this.ip = remoteInfo.address;
|
|
172
135
|
}
|
|
173
136
|
if (lightwaveMessage.fw) {
|
|
174
137
|
this.version ??= lightwaveMessage.fw;
|
|
@@ -190,27 +153,14 @@ class LightwaveRFClient extends events_1.default.EventEmitter {
|
|
|
190
153
|
if (lightwaveMessage.fn === "dim") {
|
|
191
154
|
this.emit("deviceDimmed", lightwaveMessage.room, lightwaveMessage.dev, Math.round((lightwaveMessage.param / 32) * 100));
|
|
192
155
|
}
|
|
193
|
-
|
|
194
|
-
if (!listener)
|
|
195
|
-
return;
|
|
156
|
+
// Handle retryable errors (ERR,6 = device busy)
|
|
196
157
|
if (lightwaveMessage.error?.match(/^ERR,6,/)) {
|
|
197
|
-
|
|
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));
|
|
158
|
+
this.commandQueue.handleRetryableError(lightwaveMessage.id);
|
|
203
159
|
return;
|
|
204
160
|
}
|
|
205
|
-
|
|
206
|
-
if (
|
|
207
|
-
|
|
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);
|
|
161
|
+
// Pass successful response to the queue
|
|
162
|
+
if (lightwaveMessage.id !== undefined) {
|
|
163
|
+
this.commandQueue.handleResponse(lightwaveMessage);
|
|
214
164
|
}
|
|
215
165
|
}
|
|
216
166
|
}
|
package/.dist/index.d.ts
CHANGED
|
@@ -12,6 +12,8 @@ declare class LightwaveRFConfiguration {
|
|
|
12
12
|
host?: any;
|
|
13
13
|
email?: any;
|
|
14
14
|
pin?: any;
|
|
15
|
+
linkDisplayUpdates?: boolean;
|
|
16
|
+
discoverLinkIp?: boolean;
|
|
15
17
|
}
|
|
16
18
|
declare interface ILightwaveRF {
|
|
17
19
|
on(event: "deviceTurnedOn", listener: (roomId: number, deviceId: number) => void): this;
|
|
@@ -42,7 +44,8 @@ export default class LightwaveRF extends EventEmitter<LightwaveEvents> implement
|
|
|
42
44
|
lwClient: LightwaveRFClient;
|
|
43
45
|
lwAccount: LightwaveAccount;
|
|
44
46
|
debug: debug.Debugger;
|
|
45
|
-
|
|
47
|
+
private linkDisplayUpdates;
|
|
48
|
+
constructor({ email, pin, ip, timeout, linkDisplayUpdates, discoverLinkIp, }: LightwaveRFConfiguration);
|
|
46
49
|
get serial(): string | undefined;
|
|
47
50
|
get mac(): string | undefined;
|
|
48
51
|
get uptime(): number | undefined;
|
package/.dist/index.js
CHANGED
|
@@ -17,6 +17,8 @@ class LightwaveRFConfiguration {
|
|
|
17
17
|
host;
|
|
18
18
|
email;
|
|
19
19
|
pin;
|
|
20
|
+
linkDisplayUpdates;
|
|
21
|
+
discoverLinkIp;
|
|
20
22
|
}
|
|
21
23
|
/** * LightwaveRF API
|
|
22
24
|
*
|
|
@@ -37,12 +39,16 @@ class LightwaveRF extends events_1.EventEmitter {
|
|
|
37
39
|
lwClient;
|
|
38
40
|
lwAccount;
|
|
39
41
|
debug;
|
|
40
|
-
|
|
42
|
+
linkDisplayUpdates;
|
|
43
|
+
constructor({ email, pin, ip, timeout, linkDisplayUpdates = true, discoverLinkIp = true, }) {
|
|
41
44
|
super();
|
|
42
45
|
this.setMaxListeners(255);
|
|
43
46
|
this.debug = (0, debug_1.default)("lightwave");
|
|
47
|
+
this.linkDisplayUpdates = linkDisplayUpdates;
|
|
44
48
|
this.debug("Initialising LightwaveRF Client");
|
|
45
|
-
this.lwClient = new LightwaveRFClient_1.LightwaveRFClient(this.debug, ip
|
|
49
|
+
this.lwClient = new LightwaveRFClient_1.LightwaveRFClient(this.debug, ip, {
|
|
50
|
+
discoverLinkIp,
|
|
51
|
+
});
|
|
46
52
|
this.lwClient.once("ready", () => {
|
|
47
53
|
this.debug("LightwaveRF ready");
|
|
48
54
|
});
|
|
@@ -83,36 +89,41 @@ class LightwaveRF extends events_1.EventEmitter {
|
|
|
83
89
|
}));
|
|
84
90
|
}
|
|
85
91
|
async turnOn({ roomId, deviceId, roomName, deviceName }) {
|
|
86
|
-
this.
|
|
92
|
+
const linkDisplayUpdate = this.linkDisplayUpdates
|
|
93
|
+
? `|${roomName} ${deviceName}|Turn on|`
|
|
94
|
+
: "";
|
|
95
|
+
await this.lwClient.send(`!F1R${roomId}D${deviceId}${linkDisplayUpdate}`);
|
|
87
96
|
}
|
|
88
97
|
async turnOff({ roomId, deviceId, roomName, deviceName }) {
|
|
89
|
-
this.
|
|
98
|
+
const linkDisplayUpdate = this.linkDisplayUpdates
|
|
99
|
+
? `|${roomName} ${deviceName}|Turn off|`
|
|
100
|
+
: "";
|
|
101
|
+
await this.lwClient.send(`!F0R${roomId}D${deviceId}${linkDisplayUpdate}`);
|
|
90
102
|
}
|
|
91
103
|
async dim({ roomId, deviceId, roomName, deviceName }, percentage) {
|
|
92
104
|
const lwDim = Math.round(percentage * 0.32);
|
|
93
|
-
|
|
105
|
+
const linkDisplayUpdate = this.linkDisplayUpdates
|
|
106
|
+
? `|${roomName} ${deviceName}|Dim to ${percentage}%|`
|
|
107
|
+
: "";
|
|
108
|
+
await this.lwClient.send(`!FdP${lwDim}R${roomId}D${deviceId}${linkDisplayUpdate}`);
|
|
94
109
|
}
|
|
95
110
|
async connect() {
|
|
96
111
|
return this.lwClient.connect();
|
|
97
112
|
}
|
|
98
113
|
async isRegistered() {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
return resolve(!response?.error);
|
|
103
|
-
});
|
|
104
|
-
});
|
|
114
|
+
const user = process.env.USER;
|
|
115
|
+
const response = await this.lwClient.send(`@H|Check registration|user:${user}|`);
|
|
116
|
+
return !response?.error;
|
|
105
117
|
}
|
|
106
118
|
async ensureRegistration() {
|
|
119
|
+
const user = process.env.USER;
|
|
120
|
+
const response = await this.lwClient.send(`@H|Check registration|user:${user}|`);
|
|
121
|
+
if (!response?.error) {
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
this.debug("We are not registered with the hub");
|
|
125
|
+
await this.lwClient.send("!F*p");
|
|
107
126
|
return new Promise((resolve) => {
|
|
108
|
-
const user = process.env.USER;
|
|
109
|
-
this.lwClient.send(`@H|Check registration|user:${user}|`, (response) => {
|
|
110
|
-
if (!response?.error) {
|
|
111
|
-
return resolve();
|
|
112
|
-
}
|
|
113
|
-
this.debug("We are not registered with the hub");
|
|
114
|
-
this.lwClient.send("!F*p");
|
|
115
|
-
});
|
|
116
127
|
this.lwClient.on("registered", resolve);
|
|
117
128
|
});
|
|
118
129
|
}
|
package/.dist/index.test.js
CHANGED
|
@@ -7,6 +7,7 @@ const fetch_vcr_1 = __importDefault(require("fetch-vcr"));
|
|
|
7
7
|
const vitest_1 = require("vitest");
|
|
8
8
|
const _1 = __importDefault(require("."));
|
|
9
9
|
(0, vitest_1.describe)("LightwaveRF", () => {
|
|
10
|
+
let lw;
|
|
10
11
|
(0, vitest_1.beforeAll)(() => {
|
|
11
12
|
fetch_vcr_1.default.configure({
|
|
12
13
|
fixturePath: __dirname + "/.fixtures",
|
|
@@ -14,30 +15,44 @@ const _1 = __importDefault(require("."));
|
|
|
14
15
|
});
|
|
15
16
|
global.fetch = fetch_vcr_1.default;
|
|
16
17
|
});
|
|
18
|
+
(0, vitest_1.afterEach)(async () => {
|
|
19
|
+
await lw.lwClient.disconnect();
|
|
20
|
+
});
|
|
17
21
|
(0, vitest_1.it)("should allow device linking", async () => {
|
|
18
|
-
|
|
22
|
+
lw = new _1.default({
|
|
19
23
|
email: "some@user.com",
|
|
20
24
|
pin: "1234",
|
|
21
25
|
});
|
|
22
26
|
// const devices = await lw.getDevices();
|
|
23
27
|
await lw.connect();
|
|
24
28
|
await lw.ensureRegistration();
|
|
29
|
+
await lw.lwClient.disconnect();
|
|
25
30
|
});
|
|
26
31
|
(0, vitest_1.it)("should turn device on", async () => {
|
|
27
|
-
|
|
32
|
+
lw = new _1.default({
|
|
28
33
|
email: "some@user.com",
|
|
29
34
|
pin: "1234",
|
|
35
|
+
// Disabling link display updates as they cause buffer issues in the link
|
|
36
|
+
// device
|
|
37
|
+
linkDisplayUpdates: true,
|
|
30
38
|
});
|
|
31
39
|
// const devices = await lw.getDevices();
|
|
32
40
|
await lw.connect();
|
|
33
41
|
// await lw.ensureRegistration();
|
|
34
42
|
const devices = await lw.getDevices();
|
|
35
|
-
const
|
|
36
|
-
|
|
43
|
+
const roomToInteractWith = "TV Room";
|
|
44
|
+
const lightToInteractWith = "Lights";
|
|
45
|
+
const light = devices?.find((d) => {
|
|
46
|
+
return (d.roomName === roomToInteractWith &&
|
|
47
|
+
d.deviceName === lightToInteractWith);
|
|
37
48
|
});
|
|
38
|
-
if (!
|
|
39
|
-
throw new Error(
|
|
49
|
+
if (!light) {
|
|
50
|
+
throw new Error(`Could not find ${lightToInteractWith} in the config`);
|
|
40
51
|
}
|
|
41
|
-
|
|
42
|
-
|
|
52
|
+
for (let i = 0; i < 5; i++) {
|
|
53
|
+
console.debug("Turning device on and off", i);
|
|
54
|
+
await lw.turnOn(light);
|
|
55
|
+
await lw.turnOff(light);
|
|
56
|
+
}
|
|
57
|
+
}, 30000);
|
|
43
58
|
});
|
package/package.json
CHANGED