@bobfrankston/lxlan 0.1.8 → 0.1.9
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/.hintrc +20 -0
- package/README.md +159 -0
- package/client.d.ts +0 -3
- package/client.js +19 -4
- package/device.d.ts +16 -0
- package/device.js +44 -3
- package/index.d.ts +1 -1
- package/package.json +10 -5
- package/protocol.d.ts +23 -21
- package/protocol.js +118 -62
- package/release-all.ps1 +168 -168
- package/transport.d.ts +4 -27
- package/transport.js +2 -5
- package/types.d.ts +23 -6
- package/.gitattributes +0 -5
- package/client.d.ts.map +0 -1
- package/client.js.map +0 -1
- package/device.d.ts.map +0 -1
- package/device.js.map +0 -1
- package/events.d.ts.map +0 -1
- package/events.js.map +0 -1
- package/index.d.ts.map +0 -1
- package/index.js.map +0 -1
- package/protocol.d.ts.map +0 -1
- package/protocol.js.map +0 -1
- package/transport.d.ts.map +0 -1
- package/transport.js.map +0 -1
- package/types.d.ts.map +0 -1
- package/types.js.map +0 -1
package/.hintrc
ADDED
package/README.md
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# @bobfrankston/lxlan
|
|
2
|
+
|
|
3
|
+
Core LIFX LAN protocol library. Transport-agnostic implementation of the LIFX LAN protocol for controlling LIFX devices over local networks.
|
|
4
|
+
|
|
5
|
+
## Purpose
|
|
6
|
+
|
|
7
|
+
This package provides the **protocol-level implementation** for LIFX device control. It knows:
|
|
8
|
+
- ✅ LIFX protocol messages (SetPower, SetColor, GetState, etc.)
|
|
9
|
+
- ✅ Message encoding/decoding (binary packet format)
|
|
10
|
+
- ✅ Device state management (power, color, label, etc.)
|
|
11
|
+
- ✅ Event emission patterns (device discovery, state updates)
|
|
12
|
+
|
|
13
|
+
It does **NOT** know:
|
|
14
|
+
- ❌ How to send UDP packets (no Node.js dgram)
|
|
15
|
+
- ❌ How to open WebSockets (no browser APIs)
|
|
16
|
+
- ❌ Any specific transport implementation
|
|
17
|
+
|
|
18
|
+
## Architecture Factoring
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
┌─────────────────────────────────────────────────┐
|
|
22
|
+
│ Application (your code) │
|
|
23
|
+
├─────────────────────────────────────────────────┤
|
|
24
|
+
│ @bobfrankston/lxlan (this package) │
|
|
25
|
+
│ • LxClient - device discovery & management │
|
|
26
|
+
│ • LxDevice - individual device control │
|
|
27
|
+
│ • LxProtocol - message encode/decode │
|
|
28
|
+
│ • LxTransport - INTERFACE ONLY │
|
|
29
|
+
├─────────────────────────────────────────────────┤
|
|
30
|
+
│ Transport Implementation (pick one): │
|
|
31
|
+
│ • @bobfrankston/lxlan-node → rmfudp │
|
|
32
|
+
│ • @bobfrankston/lxlan-browser → httpudp-client│
|
|
33
|
+
└─────────────────────────────────────────────────┘
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Transport Abstraction
|
|
37
|
+
|
|
38
|
+
The package exports `LxTransport` interface that must be implemented by transport layers:
|
|
39
|
+
|
|
40
|
+
```typescript
|
|
41
|
+
export interface LxTransport {
|
|
42
|
+
bind(): Promise<void>;
|
|
43
|
+
close(): void;
|
|
44
|
+
send(ip: string, port: number, data: Buffer): void;
|
|
45
|
+
broadcast(data: Buffer, port: number, addresses: string[]): void;
|
|
46
|
+
onMessage(handler: (data: Buffer, rinfo: RemoteInfo) => void): void;
|
|
47
|
+
onError(handler: (err: Error) => void): void;
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
**Implementations:**
|
|
52
|
+
- **Node.js**: `@bobfrankston/lxlan-node` provides `rmfudp`-based transport
|
|
53
|
+
- **Browser**: `@bobfrankston/lxlan-browser` provides `httpudp-client`-based transport
|
|
54
|
+
|
|
55
|
+
## Installation
|
|
56
|
+
|
|
57
|
+
This package is typically installed as a dependency of the platform-specific wrappers:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
# For Node.js projects
|
|
61
|
+
npm install @bobfrankston/lxlan-node
|
|
62
|
+
|
|
63
|
+
# For browser projects
|
|
64
|
+
npm install @bobfrankston/lxlan-browser
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Direct usage (if providing your own transport):
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
npm install @bobfrankston/lxlan
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Direct Usage (Advanced)
|
|
74
|
+
|
|
75
|
+
If you're implementing your own transport:
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
import { LxClient } from '@bobfrankston/lxlan';
|
|
79
|
+
import { MyCustomTransport } from './my-transport';
|
|
80
|
+
|
|
81
|
+
const transport = new MyCustomTransport();
|
|
82
|
+
const client = new LxClient({ transport });
|
|
83
|
+
|
|
84
|
+
await client.start();
|
|
85
|
+
|
|
86
|
+
client.on('device', (device) => {
|
|
87
|
+
console.log('Found:', device.label);
|
|
88
|
+
device.setPower(true);
|
|
89
|
+
device.setColor({ h: 120, s: 100, b: 50 });
|
|
90
|
+
});
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Features
|
|
94
|
+
|
|
95
|
+
- 🎯 Full LIFX LAN protocol support
|
|
96
|
+
- 🔍 Automatic device discovery
|
|
97
|
+
- 🎨 Color control (HSB, RGB, Kelvin)
|
|
98
|
+
- 💡 Power control
|
|
99
|
+
- 📊 Device state caching
|
|
100
|
+
- 📡 Event-driven architecture
|
|
101
|
+
- 🔄 Automatic retry with configurable timeouts
|
|
102
|
+
- 📝 Comprehensive device info (firmware, WiFi, uptime)
|
|
103
|
+
|
|
104
|
+
## Device Control
|
|
105
|
+
|
|
106
|
+
```typescript
|
|
107
|
+
// Power control
|
|
108
|
+
device.setPower(true);
|
|
109
|
+
device.setPower(false);
|
|
110
|
+
|
|
111
|
+
// Color control
|
|
112
|
+
device.setColor({ h: 180, s: 100, b: 75 }); // HSB
|
|
113
|
+
device.setColor({ r: 255, g: 0, b: 0 }); // RGB
|
|
114
|
+
device.setColor({ kelvin: 3500 }); // White temperature
|
|
115
|
+
|
|
116
|
+
// Get comprehensive device info
|
|
117
|
+
device.getDeviceInfo(); // Triggers 'deviceInfo' event
|
|
118
|
+
|
|
119
|
+
// State queries
|
|
120
|
+
console.log(device.label, device.power, device.color);
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Events
|
|
124
|
+
|
|
125
|
+
```typescript
|
|
126
|
+
client.on('device', (device) => {
|
|
127
|
+
// New device discovered
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
client.on('state', (device) => {
|
|
131
|
+
// Device state updated
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
client.on('deviceInfo', (device) => {
|
|
135
|
+
// Complete device info received (firmware, signal, uptime)
|
|
136
|
+
console.log(device.firmwareVersion, device.signal, device.uptime);
|
|
137
|
+
});
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## Dependencies
|
|
141
|
+
|
|
142
|
+
- `@bobfrankston/colorlib` - Color space conversions (HSB ↔ RGB ↔ Kelvin)
|
|
143
|
+
|
|
144
|
+
## Related Packages
|
|
145
|
+
|
|
146
|
+
- **[@bobfrankston/lxlan-node](../lxlan-node)** - Node.js transport wrapper
|
|
147
|
+
- **[@bobfrankston/lxlan-browser](../lxlan-browser)** - Browser transport wrapper
|
|
148
|
+
- **[@bobfrankston/rmfudp](../../../../utils/udp/rmfudp)** - Node.js UDP transport
|
|
149
|
+
- **[@bobfrankston/httpudp-client](../../../../utils/udp/httpudp-client)** - Browser WebSocket UDP client
|
|
150
|
+
- **[@bobfrankston/httpudp](../../../../utils/udp/httpudp)** - WebSocket-to-UDP proxy server
|
|
151
|
+
- **[@bobfrankston/colorlib](../../../../utils/colorlib)** - Color utilities
|
|
152
|
+
|
|
153
|
+
## TypeScript Configuration
|
|
154
|
+
|
|
155
|
+
This package uses `strictNullChecks: false` - undefined is treated as a first-class value throughout the codebase.
|
|
156
|
+
|
|
157
|
+
## License
|
|
158
|
+
|
|
159
|
+
MIT
|
package/client.d.ts
CHANGED
|
@@ -7,8 +7,6 @@ import { LxDevice } from './device.js';
|
|
|
7
7
|
export interface LxClientOptions {
|
|
8
8
|
/** Transport layer implementation (required) */
|
|
9
9
|
transport: LxTransport;
|
|
10
|
-
/** Broadcast addresses for discovery */
|
|
11
|
-
broadcastAddresses: string[];
|
|
12
10
|
/** Event emitter implementation (required - use Node EventEmitter or browser equivalent) */
|
|
13
11
|
eventEmitter: LxEventEmitter;
|
|
14
12
|
/** LIFX port (default 56700) */
|
|
@@ -40,7 +38,6 @@ export declare class LxClient {
|
|
|
40
38
|
private emitter;
|
|
41
39
|
private transport;
|
|
42
40
|
private port;
|
|
43
|
-
private broadcastAddresses;
|
|
44
41
|
private discoveryTimer?;
|
|
45
42
|
/** Cached devices by MAC address (lowercase) */
|
|
46
43
|
devices: Map<string, LxDevice>;
|
package/client.js
CHANGED
|
@@ -26,7 +26,6 @@ export class LxClient {
|
|
|
26
26
|
emitter;
|
|
27
27
|
transport;
|
|
28
28
|
port;
|
|
29
|
-
broadcastAddresses;
|
|
30
29
|
discoveryTimer; // NodeJS.Timeout in Node, number in browser
|
|
31
30
|
/** Cached devices by MAC address (lowercase) */
|
|
32
31
|
devices = new Map();
|
|
@@ -34,9 +33,10 @@ export class LxClient {
|
|
|
34
33
|
this.emitter = options.eventEmitter;
|
|
35
34
|
this.transport = options.transport;
|
|
36
35
|
this.port = options.port ?? LIFX_PORT;
|
|
37
|
-
this.broadcastAddresses = options.broadcastAddresses;
|
|
38
36
|
this.transport.onMessage((data, rinfo) => {
|
|
39
|
-
|
|
37
|
+
// Ensure data is Uint8Array for protocol decoder
|
|
38
|
+
const buf = data instanceof Uint8Array ? data : new Uint8Array(data);
|
|
39
|
+
this.handleMessage(buf, rinfo.address, rinfo.port);
|
|
40
40
|
});
|
|
41
41
|
this.transport.onError((err) => {
|
|
42
42
|
this.emitter.emit('error', err);
|
|
@@ -80,7 +80,7 @@ export class LxClient {
|
|
|
80
80
|
type: MessageType.GetService,
|
|
81
81
|
tagged: true
|
|
82
82
|
});
|
|
83
|
-
this.transport.broadcast(msg, this.port
|
|
83
|
+
this.transport.broadcast(msg, this.port);
|
|
84
84
|
}
|
|
85
85
|
/**
|
|
86
86
|
* Get device by MAC address
|
|
@@ -152,6 +152,7 @@ export class LxClient {
|
|
|
152
152
|
case MessageType.StateService: {
|
|
153
153
|
const info = decodeStateService(msg.payload);
|
|
154
154
|
device.port = info.port;
|
|
155
|
+
device.markResponseReceived(MessageType.GetService);
|
|
155
156
|
break;
|
|
156
157
|
}
|
|
157
158
|
case MessageType.State: {
|
|
@@ -159,16 +160,23 @@ export class LxClient {
|
|
|
159
160
|
device.color = hsbk16ToHsbk(state.hsbk);
|
|
160
161
|
device.power = state.power > 0;
|
|
161
162
|
device.label = state.label;
|
|
163
|
+
device.markResponseReceived(MessageType.Get);
|
|
164
|
+
// State messages also respond to SetPower and SetColor
|
|
165
|
+
device.markResponseReceived(MessageType.SetPower);
|
|
166
|
+
device.markResponseReceived(MessageType.SetColor);
|
|
162
167
|
this.emit('state', device);
|
|
163
168
|
break;
|
|
164
169
|
}
|
|
165
170
|
case MessageType.StatePower: {
|
|
166
171
|
device.power = decodeStatePower(msg.payload);
|
|
172
|
+
device.markResponseReceived(MessageType.GetPower);
|
|
173
|
+
device.markResponseReceived(MessageType.SetPower);
|
|
167
174
|
this.emit('power', device);
|
|
168
175
|
break;
|
|
169
176
|
}
|
|
170
177
|
case MessageType.StateLabel: {
|
|
171
178
|
device.label = decodeStateLabel(msg.payload);
|
|
179
|
+
device.markResponseReceived(MessageType.GetLabel);
|
|
172
180
|
this.emit('label', device);
|
|
173
181
|
break;
|
|
174
182
|
}
|
|
@@ -176,16 +184,19 @@ export class LxClient {
|
|
|
176
184
|
const ver = decodeStateVersion(msg.payload);
|
|
177
185
|
device.vendor = ver.vendor;
|
|
178
186
|
device.product = ver.product;
|
|
187
|
+
device.markResponseReceived(MessageType.GetVersion);
|
|
179
188
|
this.emit('version', device);
|
|
180
189
|
break;
|
|
181
190
|
}
|
|
182
191
|
case MessageType.StateGroup: {
|
|
183
192
|
device.group = decodeStateGroup(msg.payload);
|
|
193
|
+
device.markResponseReceived(MessageType.GetGroup);
|
|
184
194
|
this.emit('group', device);
|
|
185
195
|
break;
|
|
186
196
|
}
|
|
187
197
|
case MessageType.StateLocation: {
|
|
188
198
|
device.location = decodeStateGroup(msg.payload);
|
|
199
|
+
device.markResponseReceived(MessageType.GetLocation);
|
|
189
200
|
this.emit('location', device);
|
|
190
201
|
break;
|
|
191
202
|
}
|
|
@@ -193,6 +204,7 @@ export class LxClient {
|
|
|
193
204
|
const info = decodeStateHostInfo(msg.payload);
|
|
194
205
|
device.signal = info.signal;
|
|
195
206
|
device.rssi = Math.round(10 * Math.log10(info.signal));
|
|
207
|
+
device.markResponseReceived(MessageType.GetHostInfo);
|
|
196
208
|
this.emit('hostInfo', device);
|
|
197
209
|
this.checkDeviceInfoComplete(device);
|
|
198
210
|
break;
|
|
@@ -200,6 +212,7 @@ export class LxClient {
|
|
|
200
212
|
case MessageType.StateHostFirmware: {
|
|
201
213
|
const fw = decodeStateHostFirmware(msg.payload);
|
|
202
214
|
device.firmwareVersion = fw.version;
|
|
215
|
+
device.markResponseReceived(MessageType.GetHostFirmware);
|
|
203
216
|
this.emit('hostFirmware', device);
|
|
204
217
|
this.checkDeviceInfoComplete(device);
|
|
205
218
|
break;
|
|
@@ -208,6 +221,7 @@ export class LxClient {
|
|
|
208
221
|
const info = decodeStateWifiInfo(msg.payload);
|
|
209
222
|
device.signal = info.signal;
|
|
210
223
|
device.rssi = Math.round(10 * Math.log10(info.signal));
|
|
224
|
+
device.markResponseReceived(MessageType.GetWifiInfo);
|
|
211
225
|
this.emit('wifiInfo', device);
|
|
212
226
|
this.checkDeviceInfoComplete(device);
|
|
213
227
|
break;
|
|
@@ -216,6 +230,7 @@ export class LxClient {
|
|
|
216
230
|
const info = decodeStateInfo(msg.payload);
|
|
217
231
|
device.uptime = info.uptime;
|
|
218
232
|
device.downtime = info.downtime;
|
|
233
|
+
device.markResponseReceived(MessageType.GetInfo);
|
|
219
234
|
this.emit('info', device);
|
|
220
235
|
this.checkDeviceInfoComplete(device);
|
|
221
236
|
break;
|
package/device.d.ts
CHANGED
|
@@ -47,6 +47,10 @@ export declare class LxDevice {
|
|
|
47
47
|
/** Recent message sequences for deduplication (sequence -> timestamp) */
|
|
48
48
|
private recentSequences;
|
|
49
49
|
private transport?;
|
|
50
|
+
/** Pending requests awaiting responses (message type -> timeout) */
|
|
51
|
+
private pendingRequests;
|
|
52
|
+
/** Default timeout for requests in milliseconds */
|
|
53
|
+
private requestTimeout;
|
|
50
54
|
/**
|
|
51
55
|
* Create a LIFX device
|
|
52
56
|
*
|
|
@@ -69,8 +73,18 @@ export declare class LxDevice {
|
|
|
69
73
|
setTransport(transport: LxTransport): void;
|
|
70
74
|
/** Send raw message to device */
|
|
71
75
|
send(type: number, payload?: Buffer): void;
|
|
76
|
+
/** Start tracking a request with timeout */
|
|
77
|
+
private startRequestTimeout;
|
|
78
|
+
/** Clear request timeout when response received */
|
|
79
|
+
private clearRequestTimeout;
|
|
72
80
|
/** Set power on/off */
|
|
73
81
|
setPower(on: boolean): void;
|
|
82
|
+
/**
|
|
83
|
+
* Set brightness while keeping current color
|
|
84
|
+
* @param brightness - Brightness 0-100
|
|
85
|
+
* @param duration - Transition time in milliseconds (default 0)
|
|
86
|
+
*/
|
|
87
|
+
setBrightness(brightness: number, duration?: number): void;
|
|
74
88
|
/**
|
|
75
89
|
* Set color - accepts flexible input formats
|
|
76
90
|
* @param color - Color as hex "#ff0000", RGB {r,g,b}, HSL {h,s,l}, HSB {h,s,b}, HSBK {h,s,b,k}, or Kelvin number
|
|
@@ -118,6 +132,8 @@ export declare class LxDevice {
|
|
|
118
132
|
getDeviceInfo(): void;
|
|
119
133
|
/** Update last seen timestamp and online status */
|
|
120
134
|
markSeen(): void;
|
|
135
|
+
/** Notify that a response was received for a message type */
|
|
136
|
+
markResponseReceived(messageType: number): void;
|
|
121
137
|
/**
|
|
122
138
|
* Check if message is a duplicate based on sequence number.
|
|
123
139
|
* LIFX devices sometimes send duplicate responses.
|
package/device.js
CHANGED
|
@@ -47,6 +47,10 @@ export class LxDevice {
|
|
|
47
47
|
/** Recent message sequences for deduplication (sequence -> timestamp) */
|
|
48
48
|
recentSequences = new Map();
|
|
49
49
|
transport;
|
|
50
|
+
/** Pending requests awaiting responses (message type -> timeout) */
|
|
51
|
+
pendingRequests = new Map();
|
|
52
|
+
/** Default timeout for requests in milliseconds */
|
|
53
|
+
requestTimeout = 1000;
|
|
50
54
|
/**
|
|
51
55
|
* Create a LIFX device
|
|
52
56
|
*
|
|
@@ -92,6 +96,27 @@ export class LxDevice {
|
|
|
92
96
|
resRequired: true
|
|
93
97
|
});
|
|
94
98
|
this.transport.send(this.ip, this.port, msg);
|
|
99
|
+
// Track this request with a timeout
|
|
100
|
+
this.startRequestTimeout(type);
|
|
101
|
+
}
|
|
102
|
+
/** Start tracking a request with timeout */
|
|
103
|
+
startRequestTimeout(messageType) {
|
|
104
|
+
// Clear any existing timeout for this message type
|
|
105
|
+
this.clearRequestTimeout(messageType);
|
|
106
|
+
// Set new timeout
|
|
107
|
+
const timeout = setTimeout(() => {
|
|
108
|
+
this.online = false;
|
|
109
|
+
this.pendingRequests.delete(messageType);
|
|
110
|
+
}, this.requestTimeout);
|
|
111
|
+
this.pendingRequests.set(messageType, timeout);
|
|
112
|
+
}
|
|
113
|
+
/** Clear request timeout when response received */
|
|
114
|
+
clearRequestTimeout(messageType) {
|
|
115
|
+
const timeout = this.pendingRequests.get(messageType);
|
|
116
|
+
if (timeout) {
|
|
117
|
+
clearTimeout(timeout);
|
|
118
|
+
this.pendingRequests.delete(messageType);
|
|
119
|
+
}
|
|
95
120
|
}
|
|
96
121
|
/** Set power on/off */
|
|
97
122
|
setPower(on) {
|
|
@@ -103,6 +128,19 @@ export class LxDevice {
|
|
|
103
128
|
ackRequired: true
|
|
104
129
|
});
|
|
105
130
|
this.transport.send(this.ip, this.port, msg);
|
|
131
|
+
// Track this request with a timeout
|
|
132
|
+
this.startRequestTimeout(MessageType.SetPower);
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Set brightness while keeping current color
|
|
136
|
+
* @param brightness - Brightness 0-100
|
|
137
|
+
* @param duration - Transition time in milliseconds (default 0)
|
|
138
|
+
*/
|
|
139
|
+
setBrightness(brightness, duration = 0) {
|
|
140
|
+
this.requireTransport();
|
|
141
|
+
// Keep current hue/saturation/kelvin, just change brightness
|
|
142
|
+
const newColor = { ...this.color, b: brightness };
|
|
143
|
+
this.setColor(newColor, duration);
|
|
106
144
|
}
|
|
107
145
|
/**
|
|
108
146
|
* Set color - accepts flexible input formats
|
|
@@ -120,6 +158,8 @@ export class LxDevice {
|
|
|
120
158
|
ackRequired: true
|
|
121
159
|
});
|
|
122
160
|
this.transport.send(this.ip, this.port, msg);
|
|
161
|
+
// Track this request with a timeout
|
|
162
|
+
this.startRequestTimeout(MessageType.SetColor);
|
|
123
163
|
}
|
|
124
164
|
/**
|
|
125
165
|
* Set white color temperature
|
|
@@ -140,7 +180,6 @@ export class LxDevice {
|
|
|
140
180
|
}
|
|
141
181
|
/** Set label */
|
|
142
182
|
setLabel(label) {
|
|
143
|
-
this.requireTransport();
|
|
144
183
|
const msg = encodeMessage({
|
|
145
184
|
type: MessageType.SetLabel,
|
|
146
185
|
target: this.mac,
|
|
@@ -155,7 +194,6 @@ export class LxDevice {
|
|
|
155
194
|
}
|
|
156
195
|
/** Set group */
|
|
157
196
|
setGroup(id, label) {
|
|
158
|
-
this.requireTransport();
|
|
159
197
|
const msg = encodeMessage({
|
|
160
198
|
type: MessageType.SetGroup,
|
|
161
199
|
target: this.mac,
|
|
@@ -170,7 +208,6 @@ export class LxDevice {
|
|
|
170
208
|
}
|
|
171
209
|
/** Set location */
|
|
172
210
|
setLocation(id, label) {
|
|
173
|
-
this.requireTransport();
|
|
174
211
|
const msg = encodeMessage({
|
|
175
212
|
type: MessageType.SetLocation,
|
|
176
213
|
target: this.mac,
|
|
@@ -223,6 +260,10 @@ export class LxDevice {
|
|
|
223
260
|
this.lastSeen = Date.now();
|
|
224
261
|
this.online = true;
|
|
225
262
|
}
|
|
263
|
+
/** Notify that a response was received for a message type */
|
|
264
|
+
markResponseReceived(messageType) {
|
|
265
|
+
this.clearRequestTimeout(messageType);
|
|
266
|
+
}
|
|
226
267
|
/**
|
|
227
268
|
* Check if message is a duplicate based on sequence number.
|
|
228
269
|
* LIFX devices sometimes send duplicate responses.
|
package/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export * from './types.js';
|
|
2
2
|
export { LxDevice } from './device.js';
|
|
3
3
|
export { LxClient, LxClientOptions } from './client.js';
|
|
4
|
-
export { LxTransport, RemoteInfo } from './transport.js';
|
|
4
|
+
export { UdpTransport, LxTransport, RemoteInfo } from './transport.js';
|
|
5
5
|
export { LxEventEmitter, LxEventEmitterBase } from './events.js';
|
|
6
6
|
export { encodeMessage, decodeMessage } from './protocol.js';
|
|
7
7
|
//# sourceMappingURL=index.d.ts.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bobfrankston/lxlan",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.9",
|
|
4
4
|
"description": "LIFX LAN protocol library for device control via UDP",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
@@ -15,8 +15,8 @@
|
|
|
15
15
|
"build": "tsc",
|
|
16
16
|
"watch": "tsc -w",
|
|
17
17
|
"preversion": "npm run build",
|
|
18
|
-
"
|
|
19
|
-
"
|
|
18
|
+
"release": "git add -A && git diff-index --quiet HEAD || git commit -m 'Build for release' && npm version patch",
|
|
19
|
+
"postversion": "git push && git push --tags && npm publish --loglevel=error"
|
|
20
20
|
},
|
|
21
21
|
"keywords": [
|
|
22
22
|
"lifx",
|
|
@@ -31,9 +31,14 @@
|
|
|
31
31
|
"url": "https://github.com/BobFrankston/lxlan.git"
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@bobfrankston/colorlib": "
|
|
34
|
+
"@bobfrankston/colorlib": "^0.1.6",
|
|
35
|
+
"@bobfrankston/udp-transport": "^1.0.0"
|
|
35
36
|
},
|
|
36
37
|
"devDependencies": {
|
|
37
|
-
"@types/node": "^25.0.
|
|
38
|
+
"@types/node": "^25.0.9"
|
|
39
|
+
},
|
|
40
|
+
".dependencies": {
|
|
41
|
+
"@bobfrankston/colorlib": "file:../../../../utils/colorlib",
|
|
42
|
+
"@bobfrankston/udp-transport": "file:../../../../utils/udp/udp-transport"
|
|
38
43
|
}
|
|
39
44
|
}
|
package/protocol.d.ts
CHANGED
|
@@ -4,76 +4,78 @@ import { LxMessage } from './types.js';
|
|
|
4
4
|
export declare function nextSequence(): number;
|
|
5
5
|
/** Get global source ID */
|
|
6
6
|
export declare function getSource(): number;
|
|
7
|
-
/** Encode LIFX message to
|
|
7
|
+
/** Encode LIFX message to Uint8Array */
|
|
8
8
|
export declare function encodeMessage(options: {
|
|
9
9
|
type: number;
|
|
10
|
-
target?: string;
|
|
11
|
-
payload?:
|
|
10
|
+
target?: string;
|
|
11
|
+
payload?: Uint8Array;
|
|
12
12
|
tagged?: boolean;
|
|
13
13
|
ackRequired?: boolean;
|
|
14
14
|
resRequired?: boolean;
|
|
15
15
|
sequence?: number;
|
|
16
|
-
}):
|
|
17
|
-
/** Decode LIFX message from
|
|
18
|
-
export declare function decodeMessage(buf:
|
|
16
|
+
}): Uint8Array;
|
|
17
|
+
/** Decode LIFX message from Uint8Array */
|
|
18
|
+
export declare function decodeMessage(buf: Uint8Array): LxMessage;
|
|
19
19
|
/** Encode SetPower payload */
|
|
20
|
-
export declare function encodeSetPower(
|
|
20
|
+
export declare function encodeSetPower(level: boolean): Uint8Array;
|
|
21
21
|
/** Encode SetColor payload */
|
|
22
|
-
export declare function encodeSetColor(hsbk: HSBK16, duration?: number):
|
|
22
|
+
export declare function encodeSetColor(hsbk: HSBK16, duration?: number): Uint8Array;
|
|
23
23
|
/** Encode SetLabel payload */
|
|
24
|
-
export declare function encodeSetLabel(label: string):
|
|
24
|
+
export declare function encodeSetLabel(label: string): Uint8Array;
|
|
25
25
|
/** Encode SetGroup payload */
|
|
26
|
-
export declare function encodeSetGroup(id: string, label: string):
|
|
26
|
+
export declare function encodeSetGroup(id: string, label: string): Uint8Array;
|
|
27
27
|
/** Encode SetLocation payload (same format as SetGroup) */
|
|
28
|
-
export declare function encodeSetLocation(id: string, label: string):
|
|
28
|
+
export declare function encodeSetLocation(id: string, label: string): Uint8Array;
|
|
29
29
|
/** Decode State (107) payload - light state */
|
|
30
|
-
export declare function decodeState(payload:
|
|
30
|
+
export declare function decodeState(payload: Uint8Array): {
|
|
31
31
|
hsbk: HSBK16;
|
|
32
32
|
power: number;
|
|
33
33
|
label: string;
|
|
34
34
|
};
|
|
35
35
|
/** Decode StatePower (22) payload */
|
|
36
|
-
export declare function decodeStatePower(payload:
|
|
36
|
+
export declare function decodeStatePower(payload: Uint8Array): boolean;
|
|
37
37
|
/** Decode StateLabel (25) payload */
|
|
38
|
-
export declare function decodeStateLabel(payload:
|
|
38
|
+
export declare function decodeStateLabel(payload: Uint8Array): string;
|
|
39
39
|
/** Decode StateVersion (33) payload */
|
|
40
|
-
export declare function decodeStateVersion(payload:
|
|
40
|
+
export declare function decodeStateVersion(payload: Uint8Array): {
|
|
41
41
|
vendor: number;
|
|
42
42
|
product: number;
|
|
43
43
|
version: number;
|
|
44
44
|
};
|
|
45
45
|
/** Decode StateGroup (53) or StateLocation (50) payload */
|
|
46
|
-
export declare function decodeStateGroup(payload:
|
|
46
|
+
export declare function decodeStateGroup(payload: Uint8Array): {
|
|
47
47
|
id: string;
|
|
48
48
|
label: string;
|
|
49
49
|
updatedAt: number;
|
|
50
50
|
};
|
|
51
51
|
/** Decode StateService (3) payload */
|
|
52
|
-
export declare function decodeStateService(payload:
|
|
52
|
+
export declare function decodeStateService(payload: Uint8Array): {
|
|
53
53
|
service: number;
|
|
54
54
|
port: number;
|
|
55
55
|
};
|
|
56
56
|
/** Decode StateHostInfo (13) payload */
|
|
57
|
-
export declare function decodeStateHostInfo(payload:
|
|
57
|
+
export declare function decodeStateHostInfo(payload: Uint8Array): {
|
|
58
58
|
signal: number;
|
|
59
59
|
tx: number;
|
|
60
60
|
rx: number;
|
|
61
61
|
};
|
|
62
62
|
/** Decode StateHostFirmware (15) payload */
|
|
63
|
-
export declare function decodeStateHostFirmware(payload:
|
|
63
|
+
export declare function decodeStateHostFirmware(payload: Uint8Array): {
|
|
64
64
|
build: number;
|
|
65
65
|
version: number;
|
|
66
66
|
};
|
|
67
67
|
/** Decode StateWifiInfo (17) payload - same format as StateHostInfo */
|
|
68
|
-
export declare function decodeStateWifiInfo(payload:
|
|
68
|
+
export declare function decodeStateWifiInfo(payload: Uint8Array): {
|
|
69
69
|
signal: number;
|
|
70
70
|
tx: number;
|
|
71
71
|
rx: number;
|
|
72
72
|
};
|
|
73
73
|
/** Decode StateInfo (35) payload */
|
|
74
|
-
export declare function decodeStateInfo(payload:
|
|
74
|
+
export declare function decodeStateInfo(payload: Uint8Array): {
|
|
75
75
|
time: number;
|
|
76
76
|
uptime: number;
|
|
77
77
|
downtime: number;
|
|
78
78
|
};
|
|
79
|
+
/** Encode GetService payload (empty) */
|
|
80
|
+
export declare function encodeGetService(): Uint8Array;
|
|
79
81
|
//# sourceMappingURL=protocol.d.ts.map
|