@bobfrankston/lxlan 0.1.12 → 0.1.13
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/client.d.ts +9 -0
- package/client.js +24 -0
- package/device.js +4 -2
- package/package.json +1 -1
- package/protocol.d.ts +11 -0
- package/protocol.js +18 -7
package/client.d.ts
CHANGED
|
@@ -13,6 +13,8 @@ export interface LxClientOptions {
|
|
|
13
13
|
port?: number;
|
|
14
14
|
/** Auto-discovery interval in ms (0 = manual only, default 0) */
|
|
15
15
|
discoveryInterval?: number;
|
|
16
|
+
/** Enable client-to-client state broadcast for multi-client sync (default false) */
|
|
17
|
+
clientBroadcastEnabled?: boolean;
|
|
16
18
|
}
|
|
17
19
|
/**
|
|
18
20
|
* LIFX LAN client for device discovery and control.
|
|
@@ -39,6 +41,7 @@ export declare class LxClient {
|
|
|
39
41
|
private transport;
|
|
40
42
|
private port;
|
|
41
43
|
private discoveryTimer?;
|
|
44
|
+
private clientBroadcastEnabled;
|
|
42
45
|
/** Cached devices by MAC address (lowercase) */
|
|
43
46
|
devices: Map<string, LxDevice>;
|
|
44
47
|
constructor(options: LxClientOptions);
|
|
@@ -76,5 +79,11 @@ export declare class LxClient {
|
|
|
76
79
|
private handleMessage;
|
|
77
80
|
/** Check if device info is complete and emit deviceInfo event */
|
|
78
81
|
private checkDeviceInfoComplete;
|
|
82
|
+
/**
|
|
83
|
+
* Broadcast state message to other clients on the network
|
|
84
|
+
* Used for multi-client state synchronization
|
|
85
|
+
* @param stateMessage - Raw State message bytes from device
|
|
86
|
+
*/
|
|
87
|
+
private broadcastState;
|
|
79
88
|
}
|
|
80
89
|
//# sourceMappingURL=client.d.ts.map
|
package/client.js
CHANGED
|
@@ -27,12 +27,14 @@ export class LxClient {
|
|
|
27
27
|
transport;
|
|
28
28
|
port;
|
|
29
29
|
discoveryTimer; // NodeJS.Timeout in Node, number in browser
|
|
30
|
+
clientBroadcastEnabled;
|
|
30
31
|
/** Cached devices by MAC address (lowercase) */
|
|
31
32
|
devices = new Map();
|
|
32
33
|
constructor(options) {
|
|
33
34
|
this.emitter = options.eventEmitter;
|
|
34
35
|
this.transport = options.transport;
|
|
35
36
|
this.port = options.port ?? LIFX_PORT;
|
|
37
|
+
this.clientBroadcastEnabled = options.clientBroadcastEnabled ?? false;
|
|
36
38
|
this.transport.onMessage((data, rinfo) => {
|
|
37
39
|
// Ensure data is Uint8Array for protocol decoder
|
|
38
40
|
const buf = data instanceof Uint8Array ? data : new Uint8Array(data);
|
|
@@ -183,6 +185,12 @@ export class LxClient {
|
|
|
183
185
|
device.markResponseReceived(MessageType.SetPower);
|
|
184
186
|
device.markResponseReceived(MessageType.SetColor);
|
|
185
187
|
this.emit('state', device);
|
|
188
|
+
// Multi-client state synchronization: broadcast to other clients
|
|
189
|
+
// Safe fail approach: only rebroadcast if from device IP
|
|
190
|
+
// If not from device.ip, assume it's a client broadcast - just update local state, don't rebroadcast
|
|
191
|
+
if (this.clientBroadcastEnabled && fromIp === device.ip) {
|
|
192
|
+
this.broadcastState(data);
|
|
193
|
+
}
|
|
186
194
|
break;
|
|
187
195
|
}
|
|
188
196
|
case MessageType.StatePower: {
|
|
@@ -262,5 +270,21 @@ export class LxClient {
|
|
|
262
270
|
this.emit('deviceInfo', device);
|
|
263
271
|
}
|
|
264
272
|
}
|
|
273
|
+
/**
|
|
274
|
+
* Broadcast state message to other clients on the network
|
|
275
|
+
* Used for multi-client state synchronization
|
|
276
|
+
* @param stateMessage - Raw State message bytes from device
|
|
277
|
+
*/
|
|
278
|
+
broadcastState(stateMessage) {
|
|
279
|
+
try {
|
|
280
|
+
// Broadcast the State message so other clients can update their local state
|
|
281
|
+
// Using broadcast address on standard LIFX port (56700)
|
|
282
|
+
this.transport.send('255.255.255.255', this.port, stateMessage);
|
|
283
|
+
}
|
|
284
|
+
catch (err) {
|
|
285
|
+
// Don't emit error for broadcast failures - it's non-critical
|
|
286
|
+
console.warn('Failed to broadcast state to other clients:', err);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
265
289
|
}
|
|
266
290
|
//# sourceMappingURL=client.js.map
|
package/device.js
CHANGED
|
@@ -125,7 +125,8 @@ export class LxDevice {
|
|
|
125
125
|
type: MessageType.SetPower,
|
|
126
126
|
target: this.mac,
|
|
127
127
|
payload: encodeSetPower(on),
|
|
128
|
-
ackRequired: true
|
|
128
|
+
ackRequired: true,
|
|
129
|
+
resRequired: true // Get State response with full state
|
|
129
130
|
});
|
|
130
131
|
this.transport.send(this.ip, this.port, msg);
|
|
131
132
|
// Track this request with a timeout
|
|
@@ -155,7 +156,8 @@ export class LxDevice {
|
|
|
155
156
|
type: MessageType.SetColor,
|
|
156
157
|
target: this.mac,
|
|
157
158
|
payload: encodeSetColor(hsbk16, duration),
|
|
158
|
-
ackRequired: true
|
|
159
|
+
ackRequired: true,
|
|
160
|
+
resRequired: true // Get State response with full state
|
|
159
161
|
});
|
|
160
162
|
this.transport.send(this.ip, this.port, msg);
|
|
161
163
|
// Track this request with a timeout
|
package/package.json
CHANGED
package/protocol.d.ts
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
import { HSBK16 } from '@bobfrankston/colorlib';
|
|
2
2
|
import { LxMessage } from './types.js';
|
|
3
|
+
/** LIFX protocol frame flags */
|
|
4
|
+
export declare const FrameFlags: {
|
|
5
|
+
readonly ResRequired: 1;
|
|
6
|
+
readonly AckRequired: 2; /** Acknowledgement required from device */
|
|
7
|
+
};
|
|
8
|
+
/** LIFX protocol bits */
|
|
9
|
+
export declare const ProtocolBits: {
|
|
10
|
+
readonly Tagged: 8192;
|
|
11
|
+
readonly Addressable: 4096;
|
|
12
|
+
readonly ProtocolMask: 4095; /** Protocol version mask */
|
|
13
|
+
};
|
|
3
14
|
/** Get next sequence number (0-255 wrapping) */
|
|
4
15
|
export declare function nextSequence(): number;
|
|
5
16
|
/** Get global source ID */
|
package/protocol.js
CHANGED
|
@@ -1,4 +1,15 @@
|
|
|
1
1
|
import { HEADER_SIZE } from './types.js';
|
|
2
|
+
/** LIFX protocol frame flags */
|
|
3
|
+
export const FrameFlags = {
|
|
4
|
+
ResRequired: 0x01, /** Response required from device */
|
|
5
|
+
AckRequired: 0x02 /** Acknowledgement required from device */
|
|
6
|
+
};
|
|
7
|
+
/** LIFX protocol bits */
|
|
8
|
+
export const ProtocolBits = {
|
|
9
|
+
Tagged: 0x2000, /** Message is tagged (broadcast) */
|
|
10
|
+
Addressable: 0x1000, /** Message is addressable */
|
|
11
|
+
ProtocolMask: 0xFFF /** Protocol version mask */
|
|
12
|
+
};
|
|
2
13
|
let globalSource = Math.floor(Math.random() * 0xFFFFFFFF);
|
|
3
14
|
let sequenceCounter = 0;
|
|
4
15
|
/** Get next sequence number (0-255 wrapping) */
|
|
@@ -80,12 +91,12 @@ export function encodeMessage(options) {
|
|
|
80
91
|
const sequence = options.sequence ?? nextSequence();
|
|
81
92
|
// Frame (8 bytes)
|
|
82
93
|
writeUInt16LE(buf, size, 0);
|
|
83
|
-
const protocol = 1024 | (tagged ?
|
|
94
|
+
const protocol = 1024 | (tagged ? ProtocolBits.Tagged : 0) | ProtocolBits.Addressable;
|
|
84
95
|
writeUInt16LE(buf, protocol, 2);
|
|
85
96
|
writeUInt32LE(buf, globalSource, 4);
|
|
86
97
|
// Frame Address (16 bytes)
|
|
87
98
|
buf.set(macToBuffer(target), 8);
|
|
88
|
-
const flags = (options.resRequired ?
|
|
99
|
+
const flags = (options.resRequired ? FrameFlags.ResRequired : 0) | (options.ackRequired ? FrameFlags.AckRequired : 0);
|
|
89
100
|
buf[22] = flags;
|
|
90
101
|
buf[23] = sequence;
|
|
91
102
|
// Protocol Header (12 bytes)
|
|
@@ -103,9 +114,9 @@ export function decodeMessage(buf) {
|
|
|
103
114
|
}
|
|
104
115
|
const size = readUInt16LE(buf, 0);
|
|
105
116
|
const protocolBits = readUInt16LE(buf, 2);
|
|
106
|
-
const tagged = (protocolBits &
|
|
107
|
-
const addressable = (protocolBits &
|
|
108
|
-
const protocol = protocolBits &
|
|
117
|
+
const tagged = (protocolBits & ProtocolBits.Tagged) !== 0;
|
|
118
|
+
const addressable = (protocolBits & ProtocolBits.Addressable) !== 0;
|
|
119
|
+
const protocol = protocolBits & ProtocolBits.ProtocolMask;
|
|
109
120
|
const source = readUInt32LE(buf, 4);
|
|
110
121
|
const target = bufferToMac(buf, 8);
|
|
111
122
|
const flags = buf[22];
|
|
@@ -119,8 +130,8 @@ export function decodeMessage(buf) {
|
|
|
119
130
|
tagged,
|
|
120
131
|
source,
|
|
121
132
|
target,
|
|
122
|
-
resRequired: (flags &
|
|
123
|
-
ackRequired: (flags &
|
|
133
|
+
resRequired: (flags & FrameFlags.ResRequired) !== 0,
|
|
134
|
+
ackRequired: (flags & FrameFlags.AckRequired) !== 0,
|
|
124
135
|
sequence,
|
|
125
136
|
type,
|
|
126
137
|
payload
|