@felixgeelhaar/govee-api-client 1.1.0 → 2.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/README.md +160 -7
- package/dist/GoveeClient.d.ts +31 -0
- package/dist/GoveeClient.d.ts.map +1 -1
- package/dist/GoveeClient.js +33 -0
- package/dist/GoveeClient.js.map +1 -1
- package/dist/errors/GoveeApiError.d.ts +1 -1
- package/dist/errors/GoveeApiError.d.ts.map +1 -1
- package/dist/errors/GoveeApiError.js +9 -2
- package/dist/errors/GoveeApiError.js.map +1 -1
- package/dist/errors/ValidationError.d.ts +28 -0
- package/dist/errors/ValidationError.d.ts.map +1 -0
- package/dist/errors/ValidationError.js +44 -0
- package/dist/errors/ValidationError.js.map +1 -0
- package/dist/errors/index.d.ts +1 -0
- package/dist/errors/index.d.ts.map +1 -1
- package/dist/errors/index.js +1 -0
- package/dist/errors/index.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/infrastructure/GoveeDeviceRepository.d.ts.map +1 -1
- package/dist/infrastructure/GoveeDeviceRepository.js +26 -3
- package/dist/infrastructure/GoveeDeviceRepository.js.map +1 -1
- package/dist/infrastructure/SlidingWindowRateLimiter.d.ts +83 -0
- package/dist/infrastructure/SlidingWindowRateLimiter.d.ts.map +1 -0
- package/dist/infrastructure/SlidingWindowRateLimiter.js +218 -0
- package/dist/infrastructure/SlidingWindowRateLimiter.js.map +1 -0
- package/dist/infrastructure/index.d.ts +2 -0
- package/dist/infrastructure/index.d.ts.map +1 -1
- package/dist/infrastructure/index.js +2 -0
- package/dist/infrastructure/index.js.map +1 -1
- package/dist/infrastructure/response-schemas.d.ts +82 -0
- package/dist/infrastructure/response-schemas.d.ts.map +1 -0
- package/dist/infrastructure/response-schemas.js +59 -0
- package/dist/infrastructure/response-schemas.js.map +1 -0
- package/dist/infrastructure/retry/IntegrationGuide.d.ts +133 -0
- package/dist/infrastructure/retry/IntegrationGuide.d.ts.map +1 -0
- package/dist/infrastructure/retry/IntegrationGuide.js +295 -0
- package/dist/infrastructure/retry/IntegrationGuide.js.map +1 -0
- package/dist/infrastructure/retry/RetryConfigPresets.d.ts +114 -0
- package/dist/infrastructure/retry/RetryConfigPresets.d.ts.map +1 -0
- package/dist/infrastructure/retry/RetryConfigPresets.js +406 -0
- package/dist/infrastructure/retry/RetryConfigPresets.js.map +1 -0
- package/dist/infrastructure/retry/RetryPolicy.d.ts +148 -0
- package/dist/infrastructure/retry/RetryPolicy.d.ts.map +1 -0
- package/dist/infrastructure/retry/RetryPolicy.js +373 -0
- package/dist/infrastructure/retry/RetryPolicy.js.map +1 -0
- package/dist/infrastructure/retry/RetryableRepository.d.ts +75 -0
- package/dist/infrastructure/retry/RetryableRepository.d.ts.map +1 -0
- package/dist/infrastructure/retry/RetryableRepository.js +142 -0
- package/dist/infrastructure/retry/RetryableRepository.js.map +1 -0
- package/dist/infrastructure/retry/RetryableRequest.d.ts +132 -0
- package/dist/infrastructure/retry/RetryableRequest.d.ts.map +1 -0
- package/dist/infrastructure/retry/RetryableRequest.js +248 -0
- package/dist/infrastructure/retry/RetryableRequest.js.map +1 -0
- package/dist/infrastructure/retry/index.d.ts +35 -0
- package/dist/infrastructure/retry/index.d.ts.map +1 -0
- package/dist/infrastructure/retry/index.js +36 -0
- package/dist/infrastructure/retry/index.js.map +1 -0
- package/dist/services/GoveeControlService.d.ts +53 -0
- package/dist/services/GoveeControlService.d.ts.map +1 -1
- package/dist/services/GoveeControlService.js +138 -12
- package/dist/services/GoveeControlService.js.map +1 -1
- package/docs/EXAMPLES.md +799 -0
- package/docs/LLM_API_REFERENCE.md +425 -0
- package/docs/TYPE_DEFINITIONS.md +803 -0
- package/package.json +25 -16
package/docs/EXAMPLES.md
ADDED
|
@@ -0,0 +1,799 @@
|
|
|
1
|
+
# Govee API Client - Examples
|
|
2
|
+
|
|
3
|
+
This document provides comprehensive examples for using the Govee API Client library. Each example is complete and can be run as-is with a valid API key.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
- [Basic Setup](#basic-setup)
|
|
8
|
+
- [Device Discovery](#device-discovery)
|
|
9
|
+
- [Device Control](#device-control)
|
|
10
|
+
- [Color Management](#color-management)
|
|
11
|
+
- [State Management](#state-management)
|
|
12
|
+
- [Error Handling](#error-handling)
|
|
13
|
+
- [Rate Limiting & Monitoring](#rate-limiting--monitoring)
|
|
14
|
+
- [Retry Configuration](#retry-configuration)
|
|
15
|
+
- [Advanced Usage](#advanced-usage)
|
|
16
|
+
|
|
17
|
+
## Basic Setup
|
|
18
|
+
|
|
19
|
+
### Simple Client Initialization
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
import { GoveeClient } from '@felixgeelhaar/govee-api-client';
|
|
23
|
+
|
|
24
|
+
const client = new GoveeClient({
|
|
25
|
+
apiKey: 'your-govee-api-key-here',
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
// Test connection
|
|
29
|
+
try {
|
|
30
|
+
const devices = await client.getDevices();
|
|
31
|
+
console.log(`Successfully connected! Found ${devices.length} devices.`);
|
|
32
|
+
} catch (error) {
|
|
33
|
+
console.error('Failed to connect:', error.message);
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### Production Configuration
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
import { GoveeClient } from '@felixgeelhaar/govee-api-client';
|
|
41
|
+
import pino from 'pino';
|
|
42
|
+
|
|
43
|
+
const client = new GoveeClient({
|
|
44
|
+
apiKey: process.env.GOVEE_API_KEY!,
|
|
45
|
+
timeout: 30000, // 30 second timeout
|
|
46
|
+
rateLimit: 90, // 90 requests per minute (conservative)
|
|
47
|
+
logger: pino({
|
|
48
|
+
// Structured logging
|
|
49
|
+
level: 'info',
|
|
50
|
+
transport: {
|
|
51
|
+
target: 'pino-pretty',
|
|
52
|
+
options: { colorize: true },
|
|
53
|
+
},
|
|
54
|
+
}),
|
|
55
|
+
enableRetries: true, // Enable retry logic
|
|
56
|
+
retryPolicy: 'production', // Production retry settings
|
|
57
|
+
});
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Device Discovery
|
|
61
|
+
|
|
62
|
+
### List All Devices
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
async function listAllDevices() {
|
|
66
|
+
try {
|
|
67
|
+
const devices = await client.getDevices();
|
|
68
|
+
|
|
69
|
+
console.log(`Found ${devices.length} devices:`);
|
|
70
|
+
devices.forEach(device => {
|
|
71
|
+
console.log(`- ${device.deviceName} (${device.model})`);
|
|
72
|
+
console.log(` ID: ${device.deviceId}`);
|
|
73
|
+
console.log(` Controllable: ${device.controllable}`);
|
|
74
|
+
console.log(` Supported commands: ${device.supportedCmds.join(', ')}`);
|
|
75
|
+
console.log('');
|
|
76
|
+
});
|
|
77
|
+
} catch (error) {
|
|
78
|
+
console.error('Failed to list devices:', error.message);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Find Specific Devices
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
async function findDevicesByName() {
|
|
87
|
+
// Case-insensitive search
|
|
88
|
+
const livingRoomLight = await client.findDeviceByName('living room');
|
|
89
|
+
|
|
90
|
+
if (livingRoomLight) {
|
|
91
|
+
console.log('Found living room light:', livingRoomLight.deviceName);
|
|
92
|
+
} else {
|
|
93
|
+
console.log('Living room light not found');
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async function findDevicesByModel() {
|
|
98
|
+
const h6159Devices = await client.findDevicesByModel('H6159');
|
|
99
|
+
|
|
100
|
+
console.log(`Found ${h6159Devices.length} H6159 devices:`);
|
|
101
|
+
h6159Devices.forEach(device => {
|
|
102
|
+
console.log(`- ${device.deviceName} (${device.deviceId})`);
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Filter Controllable Devices
|
|
108
|
+
|
|
109
|
+
```typescript
|
|
110
|
+
async function getControllableDevices() {
|
|
111
|
+
const controllableDevices = await client.getControllableDevices();
|
|
112
|
+
|
|
113
|
+
console.log('Controllable devices:');
|
|
114
|
+
controllableDevices.forEach(device => {
|
|
115
|
+
console.log(`- ${device.deviceName}: ${device.supportedCmds.join(', ')}`);
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Device Control
|
|
121
|
+
|
|
122
|
+
### Basic Power Control
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
async function basicPowerControl(deviceId: string, model: string) {
|
|
126
|
+
try {
|
|
127
|
+
// Turn on
|
|
128
|
+
await client.turnOn(deviceId, model);
|
|
129
|
+
console.log('Device turned on');
|
|
130
|
+
|
|
131
|
+
// Wait a moment
|
|
132
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
133
|
+
|
|
134
|
+
// Turn off
|
|
135
|
+
await client.turnOff(deviceId, model);
|
|
136
|
+
console.log('Device turned off');
|
|
137
|
+
} catch (error) {
|
|
138
|
+
console.error('Power control failed:', error.message);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### Brightness Control
|
|
144
|
+
|
|
145
|
+
```typescript
|
|
146
|
+
import { Brightness } from '@felixgeelhaar/govee-api-client';
|
|
147
|
+
|
|
148
|
+
async function brightnessControl(deviceId: string, model: string) {
|
|
149
|
+
try {
|
|
150
|
+
// Set to 25% brightness
|
|
151
|
+
await client.setBrightness(deviceId, model, Brightness.dim());
|
|
152
|
+
console.log('Set to dim (25%)');
|
|
153
|
+
|
|
154
|
+
// Set to 50% brightness
|
|
155
|
+
await client.setBrightness(deviceId, model, Brightness.medium());
|
|
156
|
+
console.log('Set to medium (50%)');
|
|
157
|
+
|
|
158
|
+
// Set to 75% brightness
|
|
159
|
+
await client.setBrightness(deviceId, model, Brightness.bright());
|
|
160
|
+
console.log('Set to bright (75%)');
|
|
161
|
+
|
|
162
|
+
// Custom brightness
|
|
163
|
+
await client.setBrightness(deviceId, model, new Brightness(85));
|
|
164
|
+
console.log('Set to custom brightness (85%)');
|
|
165
|
+
} catch (error) {
|
|
166
|
+
console.error('Brightness control failed:', error.message);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### Turn On With Settings
|
|
172
|
+
|
|
173
|
+
```typescript
|
|
174
|
+
import { ColorRgb, ColorTemperature, Brightness } from '@felixgeelhaar/govee-api-client';
|
|
175
|
+
|
|
176
|
+
async function turnOnWithSettings(deviceId: string, model: string) {
|
|
177
|
+
try {
|
|
178
|
+
// Turn on with brightness
|
|
179
|
+
await client.turnOnWithBrightness(deviceId, model, new Brightness(60));
|
|
180
|
+
console.log('Turned on with 60% brightness');
|
|
181
|
+
|
|
182
|
+
// Turn on with red color
|
|
183
|
+
const red = new ColorRgb(255, 0, 0);
|
|
184
|
+
await client.turnOnWithColor(deviceId, model, red, new Brightness(80));
|
|
185
|
+
console.log('Turned on with red color at 80% brightness');
|
|
186
|
+
|
|
187
|
+
// Turn on with warm white
|
|
188
|
+
const warmWhite = ColorTemperature.warmWhite();
|
|
189
|
+
await client.turnOnWithColorTemperature(deviceId, model, warmWhite, new Brightness(100));
|
|
190
|
+
console.log('Turned on with warm white at 100% brightness');
|
|
191
|
+
} catch (error) {
|
|
192
|
+
console.error('Turn on with settings failed:', error.message);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
## Color Management
|
|
198
|
+
|
|
199
|
+
### RGB Color Control
|
|
200
|
+
|
|
201
|
+
```typescript
|
|
202
|
+
import { ColorRgb } from '@felixgeelhaar/govee-api-client';
|
|
203
|
+
|
|
204
|
+
async function rgbColorControl(deviceId: string, model: string) {
|
|
205
|
+
try {
|
|
206
|
+
// Primary colors
|
|
207
|
+
await client.setColor(deviceId, model, new ColorRgb(255, 0, 0)); // Red
|
|
208
|
+
console.log('Set to red');
|
|
209
|
+
|
|
210
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
211
|
+
|
|
212
|
+
await client.setColor(deviceId, model, new ColorRgb(0, 255, 0)); // Green
|
|
213
|
+
console.log('Set to green');
|
|
214
|
+
|
|
215
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
216
|
+
|
|
217
|
+
await client.setColor(deviceId, model, new ColorRgb(0, 0, 255)); // Blue
|
|
218
|
+
console.log('Set to blue');
|
|
219
|
+
|
|
220
|
+
// Create color from hex
|
|
221
|
+
const purple = ColorRgb.fromHex('#800080');
|
|
222
|
+
await client.setColor(deviceId, model, purple);
|
|
223
|
+
console.log('Set to purple from hex');
|
|
224
|
+
|
|
225
|
+
// Create color from object
|
|
226
|
+
const cyan = ColorRgb.fromObject({ r: 0, g: 255, b: 255 });
|
|
227
|
+
await client.setColor(deviceId, model, cyan);
|
|
228
|
+
console.log('Set to cyan from object');
|
|
229
|
+
} catch (error) {
|
|
230
|
+
console.error('RGB color control failed:', error.message);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
### Color Temperature Control
|
|
236
|
+
|
|
237
|
+
```typescript
|
|
238
|
+
import { ColorTemperature } from '@felixgeelhaar/govee-api-client';
|
|
239
|
+
|
|
240
|
+
async function colorTemperatureControl(deviceId: string, model: string) {
|
|
241
|
+
try {
|
|
242
|
+
// Preset temperatures
|
|
243
|
+
await client.setColorTemperature(deviceId, model, ColorTemperature.warmWhite());
|
|
244
|
+
console.log('Set to warm white (2700K)');
|
|
245
|
+
|
|
246
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
247
|
+
|
|
248
|
+
await client.setColorTemperature(deviceId, model, ColorTemperature.daylight());
|
|
249
|
+
console.log('Set to daylight (5600K)');
|
|
250
|
+
|
|
251
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
252
|
+
|
|
253
|
+
await client.setColorTemperature(deviceId, model, ColorTemperature.coolWhite());
|
|
254
|
+
console.log('Set to cool white (6500K)');
|
|
255
|
+
|
|
256
|
+
// Custom temperature
|
|
257
|
+
const customTemp = new ColorTemperature(4000);
|
|
258
|
+
await client.setColorTemperature(deviceId, model, customTemp);
|
|
259
|
+
console.log('Set to custom temperature (4000K)');
|
|
260
|
+
|
|
261
|
+
// Check if temperature is warm or cool
|
|
262
|
+
console.log(`Is warm: ${customTemp.isWarm()}`); // true (< 4000K)
|
|
263
|
+
console.log(`Is cool: ${customTemp.isCool()}`); // false (> 5000K)
|
|
264
|
+
} catch (error) {
|
|
265
|
+
console.error('Color temperature control failed:', error.message);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
### Color Utilities
|
|
271
|
+
|
|
272
|
+
```typescript
|
|
273
|
+
import { ColorRgb } from '@felixgeelhaar/govee-api-client';
|
|
274
|
+
|
|
275
|
+
function colorUtilities() {
|
|
276
|
+
const red = new ColorRgb(255, 0, 0);
|
|
277
|
+
|
|
278
|
+
// Color information
|
|
279
|
+
console.log('RGB values:', red.r, red.g, red.b);
|
|
280
|
+
console.log('Hex representation:', red.toHex()); // "#ff0000"
|
|
281
|
+
console.log('String representation:', red.toString()); // "rgb(255, 0, 0)"
|
|
282
|
+
console.log('Object representation:', red.toObject()); // { r: 255, g: 0, b: 0 }
|
|
283
|
+
|
|
284
|
+
// Color comparison
|
|
285
|
+
const anotherRed = new ColorRgb(255, 0, 0);
|
|
286
|
+
const blue = new ColorRgb(0, 0, 255);
|
|
287
|
+
|
|
288
|
+
console.log('Colors equal:', red.equals(anotherRed)); // true
|
|
289
|
+
console.log('Colors equal:', red.equals(blue)); // false
|
|
290
|
+
}
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
## State Management
|
|
294
|
+
|
|
295
|
+
### Check Device State
|
|
296
|
+
|
|
297
|
+
```typescript
|
|
298
|
+
async function checkDeviceState(deviceId: string, model: string) {
|
|
299
|
+
try {
|
|
300
|
+
const state = await client.getDeviceState(deviceId, model);
|
|
301
|
+
|
|
302
|
+
console.log('Device State:');
|
|
303
|
+
console.log(`- Online: ${state.isOnline()}`);
|
|
304
|
+
console.log(`- Power: ${state.getPowerState()}`);
|
|
305
|
+
console.log(`- Brightness: ${state.getBrightness()}%`);
|
|
306
|
+
|
|
307
|
+
const color = state.getColor();
|
|
308
|
+
if (color) {
|
|
309
|
+
console.log(`- Color: rgb(${color.r}, ${color.g}, ${color.b})`);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
const temp = state.getColorTemperature();
|
|
313
|
+
if (temp) {
|
|
314
|
+
console.log(`- Color Temperature: ${temp}K`);
|
|
315
|
+
}
|
|
316
|
+
} catch (error) {
|
|
317
|
+
console.error('Failed to get device state:', error.message);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
### Monitor Device Status
|
|
323
|
+
|
|
324
|
+
```typescript
|
|
325
|
+
async function monitorDeviceStatus(deviceId: string, model: string) {
|
|
326
|
+
try {
|
|
327
|
+
// Check if device is online
|
|
328
|
+
const isOnline = await client.isDeviceOnline(deviceId, model);
|
|
329
|
+
console.log(`Device online: ${isOnline}`);
|
|
330
|
+
|
|
331
|
+
// Check if device is powered on
|
|
332
|
+
const isPoweredOn = await client.isDevicePoweredOn(deviceId, model);
|
|
333
|
+
console.log(`Device powered on: ${isPoweredOn}`);
|
|
334
|
+
|
|
335
|
+
// Only control if device is online
|
|
336
|
+
if (isOnline) {
|
|
337
|
+
await client.turnOn(deviceId, model);
|
|
338
|
+
console.log('Device turned on');
|
|
339
|
+
} else {
|
|
340
|
+
console.log('Device is offline, cannot control');
|
|
341
|
+
}
|
|
342
|
+
} catch (error) {
|
|
343
|
+
console.error('Device monitoring failed:', error.message);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
## Error Handling
|
|
349
|
+
|
|
350
|
+
### Comprehensive Error Handling
|
|
351
|
+
|
|
352
|
+
```typescript
|
|
353
|
+
import {
|
|
354
|
+
GoveeApiError,
|
|
355
|
+
InvalidApiKeyError,
|
|
356
|
+
RateLimitError,
|
|
357
|
+
NetworkError,
|
|
358
|
+
ValidationError,
|
|
359
|
+
} from '@felixgeelhaar/govee-api-client';
|
|
360
|
+
|
|
361
|
+
async function comprehensiveErrorHandling() {
|
|
362
|
+
try {
|
|
363
|
+
const devices = await client.getDevices();
|
|
364
|
+
// ... perform operations
|
|
365
|
+
} catch (error) {
|
|
366
|
+
if (error instanceof InvalidApiKeyError) {
|
|
367
|
+
console.error('❌ Invalid API Key');
|
|
368
|
+
console.log('💡 Suggestion:', error.getRecommendation());
|
|
369
|
+
// Handle: Get new API key, update configuration
|
|
370
|
+
} else if (error instanceof RateLimitError) {
|
|
371
|
+
console.error('⏱️ Rate Limited');
|
|
372
|
+
console.log(`⏳ Retry after: ${error.getRetryAfterMs()}ms`);
|
|
373
|
+
// Handle: Wait and retry, or queue for later
|
|
374
|
+
} else if (error instanceof GoveeApiError) {
|
|
375
|
+
console.error('🔌 API Error:', error.message);
|
|
376
|
+
console.log('💡 Suggestion:', error.getRecommendation());
|
|
377
|
+
|
|
378
|
+
if (error.isDeviceOffline()) {
|
|
379
|
+
console.log('📴 Device is currently offline');
|
|
380
|
+
// Handle: Skip this device, notify user
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
if (error.statusCode) {
|
|
384
|
+
console.log(`🔢 Status Code: ${error.statusCode}`);
|
|
385
|
+
}
|
|
386
|
+
} else if (error instanceof NetworkError) {
|
|
387
|
+
console.error('🌐 Network Error:', error.message);
|
|
388
|
+
console.log(`🔄 Retryable: ${error.isRetryable()}`);
|
|
389
|
+
|
|
390
|
+
if (error.isRetryable()) {
|
|
391
|
+
// Implement retry logic
|
|
392
|
+
console.log('Will retry this operation...');
|
|
393
|
+
}
|
|
394
|
+
} else if (error instanceof ValidationError) {
|
|
395
|
+
console.error('✅ Validation Error:', error.message);
|
|
396
|
+
if (error.field) {
|
|
397
|
+
console.log(`📍 Field: ${error.field}`);
|
|
398
|
+
console.log(`💥 Value: ${error.value}`);
|
|
399
|
+
}
|
|
400
|
+
} else {
|
|
401
|
+
console.error('❓ Unknown Error:', error);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
### Retry Pattern
|
|
408
|
+
|
|
409
|
+
```typescript
|
|
410
|
+
async function retryPattern(operation: () => Promise<void>, maxRetries = 3) {
|
|
411
|
+
let attempt = 0;
|
|
412
|
+
|
|
413
|
+
while (attempt < maxRetries) {
|
|
414
|
+
try {
|
|
415
|
+
await operation();
|
|
416
|
+
return; // Success!
|
|
417
|
+
} catch (error) {
|
|
418
|
+
attempt++;
|
|
419
|
+
|
|
420
|
+
if (error instanceof RateLimitError && attempt < maxRetries) {
|
|
421
|
+
const delay = error.getRetryAfterMs();
|
|
422
|
+
console.log(`Rate limited, waiting ${delay}ms before retry ${attempt}/${maxRetries}`);
|
|
423
|
+
await new Promise(resolve => setTimeout(resolve, delay));
|
|
424
|
+
continue;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
if (error instanceof NetworkError && error.isRetryable() && attempt < maxRetries) {
|
|
428
|
+
const delay = Math.pow(2, attempt) * 1000; // Exponential backoff
|
|
429
|
+
console.log(`Network error, waiting ${delay}ms before retry ${attempt}/${maxRetries}`);
|
|
430
|
+
await new Promise(resolve => setTimeout(resolve, delay));
|
|
431
|
+
continue;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// Not retryable or max retries reached
|
|
435
|
+
throw error;
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
## Rate Limiting & Monitoring
|
|
442
|
+
|
|
443
|
+
### Monitor Rate Limiter
|
|
444
|
+
|
|
445
|
+
```typescript
|
|
446
|
+
function monitorRateLimiter() {
|
|
447
|
+
const stats = client.getRateLimiterStats();
|
|
448
|
+
|
|
449
|
+
console.log('Rate Limiter Status:');
|
|
450
|
+
console.log(`- Current requests: ${stats.currentRequests}/${stats.maxRequests}`);
|
|
451
|
+
console.log(`- Utilization: ${stats.utilizationPercent.toFixed(1)}%`);
|
|
452
|
+
console.log(`- Queue size: ${stats.queueSize}`);
|
|
453
|
+
console.log(`- Can execute immediately: ${stats.canExecuteImmediately}`);
|
|
454
|
+
|
|
455
|
+
if (stats.nextAvailableSlot) {
|
|
456
|
+
console.log(`- Next slot available: ${stats.nextAvailableSlot.toISOString()}`);
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// Alert if utilization is high
|
|
460
|
+
if (stats.utilizationPercent > 80) {
|
|
461
|
+
console.warn('⚠️ High rate limiter utilization, consider reducing request frequency');
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
### Monitor Service Performance
|
|
467
|
+
|
|
468
|
+
```typescript
|
|
469
|
+
function monitorServicePerformance() {
|
|
470
|
+
const serviceStats = client.getServiceStats();
|
|
471
|
+
|
|
472
|
+
console.log('Service Statistics:');
|
|
473
|
+
console.log('Rate Limiter:', {
|
|
474
|
+
utilization: `${serviceStats.rateLimiter.utilizationPercent.toFixed(1)}%`,
|
|
475
|
+
queue: serviceStats.rateLimiter.queueSize,
|
|
476
|
+
canExecute: serviceStats.rateLimiter.canExecuteImmediately,
|
|
477
|
+
});
|
|
478
|
+
|
|
479
|
+
if (serviceStats.retries) {
|
|
480
|
+
console.log('Retry Metrics:', {
|
|
481
|
+
attempts: serviceStats.retries.totalAttempts,
|
|
482
|
+
successful: serviceStats.retries.successfulRetries,
|
|
483
|
+
failed: serviceStats.retries.failedRetries,
|
|
484
|
+
averageDelay: `${serviceStats.retries.averageRetryDelayMs.toFixed(0)}ms`,
|
|
485
|
+
circuitBreaker: serviceStats.retries.circuitBreakerState,
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
console.log('Configuration:', serviceStats.configuration);
|
|
490
|
+
}
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
## Retry Configuration
|
|
494
|
+
|
|
495
|
+
### Development Environment
|
|
496
|
+
|
|
497
|
+
```typescript
|
|
498
|
+
const devClient = new GoveeClient({
|
|
499
|
+
apiKey: process.env.GOVEE_API_KEY!,
|
|
500
|
+
enableRetries: true,
|
|
501
|
+
retryPolicy: 'development', // More retries, shorter delays
|
|
502
|
+
logger: pino({ level: 'debug' }),
|
|
503
|
+
});
|
|
504
|
+
```
|
|
505
|
+
|
|
506
|
+
### Custom Retry Policy
|
|
507
|
+
|
|
508
|
+
```typescript
|
|
509
|
+
import { GoveeClient, RetryPolicy } from '@felixgeelhaar/govee-api-client';
|
|
510
|
+
|
|
511
|
+
const customRetryPolicy = new RetryPolicy({
|
|
512
|
+
backoff: {
|
|
513
|
+
type: 'exponential',
|
|
514
|
+
initialDelayMs: 2000, // Start with 2 second delay
|
|
515
|
+
maxDelayMs: 60000, // Max 1 minute delay
|
|
516
|
+
multiplier: 2.0, // Double delay each retry
|
|
517
|
+
},
|
|
518
|
+
jitter: {
|
|
519
|
+
type: 'equal', // Add randomization
|
|
520
|
+
factor: 0.1, // ±10% jitter
|
|
521
|
+
},
|
|
522
|
+
condition: {
|
|
523
|
+
maxAttempts: 5, // Maximum 5 attempts
|
|
524
|
+
maxTotalTimeMs: 300000, // Give up after 5 minutes total
|
|
525
|
+
retryableStatusCodes: [408, 429, 502, 503, 504],
|
|
526
|
+
retryableErrorTypes: [RateLimitError, NetworkError],
|
|
527
|
+
},
|
|
528
|
+
circuitBreaker: {
|
|
529
|
+
enabled: true,
|
|
530
|
+
failureThreshold: 10, // Open after 10 failures
|
|
531
|
+
recoveryTimeoutMs: 60000, // Try again after 1 minute
|
|
532
|
+
halfOpenSuccessThreshold: 3, // Need 3 successes to close
|
|
533
|
+
},
|
|
534
|
+
enableMetrics: true,
|
|
535
|
+
});
|
|
536
|
+
|
|
537
|
+
const client = new GoveeClient({
|
|
538
|
+
apiKey: process.env.GOVEE_API_KEY!,
|
|
539
|
+
enableRetries: true,
|
|
540
|
+
retryPolicy: customRetryPolicy,
|
|
541
|
+
});
|
|
542
|
+
```
|
|
543
|
+
|
|
544
|
+
## Advanced Usage
|
|
545
|
+
|
|
546
|
+
### Command Pattern
|
|
547
|
+
|
|
548
|
+
```typescript
|
|
549
|
+
import { CommandFactory, ColorRgb, Brightness } from '@felixgeelhaar/govee-api-client';
|
|
550
|
+
|
|
551
|
+
async function useCommandPattern(deviceId: string, model: string) {
|
|
552
|
+
// Create commands manually
|
|
553
|
+
const powerOn = CommandFactory.powerOn();
|
|
554
|
+
const setBrightness = CommandFactory.brightness(new Brightness(75));
|
|
555
|
+
const setColor = CommandFactory.color(new ColorRgb(255, 0, 0));
|
|
556
|
+
|
|
557
|
+
// Send commands
|
|
558
|
+
await client.sendCommand(deviceId, model, powerOn);
|
|
559
|
+
await client.sendCommand(deviceId, model, setBrightness);
|
|
560
|
+
await client.sendCommand(deviceId, model, setColor);
|
|
561
|
+
}
|
|
562
|
+
```
|
|
563
|
+
|
|
564
|
+
### Batch Operations
|
|
565
|
+
|
|
566
|
+
```typescript
|
|
567
|
+
async function batchOperations() {
|
|
568
|
+
const devices = await client.getControllableDevices();
|
|
569
|
+
|
|
570
|
+
// Turn on all devices with same color
|
|
571
|
+
const red = new ColorRgb(255, 0, 0);
|
|
572
|
+
const promises = devices.map(device =>
|
|
573
|
+
client.turnOnWithColor(device.deviceId, device.model, red)
|
|
574
|
+
);
|
|
575
|
+
|
|
576
|
+
try {
|
|
577
|
+
await Promise.all(promises);
|
|
578
|
+
console.log('All devices set to red');
|
|
579
|
+
} catch (error) {
|
|
580
|
+
console.error('Some operations failed:', error.message);
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
```
|
|
584
|
+
|
|
585
|
+
### Scene Management
|
|
586
|
+
|
|
587
|
+
```typescript
|
|
588
|
+
import { ColorRgb, ColorTemperature, Brightness } from '@felixgeelhaar/govee-api-client';
|
|
589
|
+
|
|
590
|
+
interface Scene {
|
|
591
|
+
name: string;
|
|
592
|
+
devices: Array<{
|
|
593
|
+
deviceId: string;
|
|
594
|
+
model: string;
|
|
595
|
+
color?: ColorRgb;
|
|
596
|
+
colorTemperature?: ColorTemperature;
|
|
597
|
+
brightness: Brightness;
|
|
598
|
+
}>;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
async function applyScene(scene: Scene) {
|
|
602
|
+
console.log(`Applying scene: ${scene.name}`);
|
|
603
|
+
|
|
604
|
+
const promises = scene.devices.map(async device => {
|
|
605
|
+
if (device.color) {
|
|
606
|
+
await client.turnOnWithColor(device.deviceId, device.model, device.color, device.brightness);
|
|
607
|
+
} else if (device.colorTemperature) {
|
|
608
|
+
await client.turnOnWithColorTemperature(
|
|
609
|
+
device.deviceId,
|
|
610
|
+
device.model,
|
|
611
|
+
device.colorTemperature,
|
|
612
|
+
device.brightness
|
|
613
|
+
);
|
|
614
|
+
} else {
|
|
615
|
+
await client.turnOnWithBrightness(device.deviceId, device.model, device.brightness);
|
|
616
|
+
}
|
|
617
|
+
});
|
|
618
|
+
|
|
619
|
+
await Promise.all(promises);
|
|
620
|
+
console.log(`Scene "${scene.name}" applied successfully`);
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
// Example scenes
|
|
624
|
+
const movieScene: Scene = {
|
|
625
|
+
name: 'Movie Night',
|
|
626
|
+
devices: [
|
|
627
|
+
{
|
|
628
|
+
deviceId: 'living-room-main',
|
|
629
|
+
model: 'H6159',
|
|
630
|
+
color: new ColorRgb(100, 0, 200), // Purple
|
|
631
|
+
brightness: new Brightness(20),
|
|
632
|
+
},
|
|
633
|
+
{
|
|
634
|
+
deviceId: 'living-room-accent',
|
|
635
|
+
model: 'H6003',
|
|
636
|
+
colorTemperature: ColorTemperature.warmWhite(),
|
|
637
|
+
brightness: new Brightness(10),
|
|
638
|
+
},
|
|
639
|
+
],
|
|
640
|
+
};
|
|
641
|
+
```
|
|
642
|
+
|
|
643
|
+
### Environment-Specific Configuration
|
|
644
|
+
|
|
645
|
+
```typescript
|
|
646
|
+
import { GoveeClient } from '@felixgeelhaar/govee-api-client';
|
|
647
|
+
import pino from 'pino';
|
|
648
|
+
|
|
649
|
+
function createClient() {
|
|
650
|
+
const isDevelopment = process.env.NODE_ENV === 'development';
|
|
651
|
+
const isProduction = process.env.NODE_ENV === 'production';
|
|
652
|
+
|
|
653
|
+
return new GoveeClient({
|
|
654
|
+
apiKey: process.env.GOVEE_API_KEY!,
|
|
655
|
+
timeout: isDevelopment ? 10000 : 30000,
|
|
656
|
+
rateLimit: isDevelopment ? 50 : 95,
|
|
657
|
+
logger: isProduction
|
|
658
|
+
? pino({ level: 'warn' })
|
|
659
|
+
: pino({ level: 'debug', transport: { target: 'pino-pretty' } }),
|
|
660
|
+
enableRetries: isProduction,
|
|
661
|
+
retryPolicy: isProduction ? 'production' : 'development',
|
|
662
|
+
});
|
|
663
|
+
}
|
|
664
|
+
```
|
|
665
|
+
|
|
666
|
+
## Complete Application Example
|
|
667
|
+
|
|
668
|
+
```typescript
|
|
669
|
+
import {
|
|
670
|
+
GoveeClient,
|
|
671
|
+
ColorRgb,
|
|
672
|
+
ColorTemperature,
|
|
673
|
+
Brightness,
|
|
674
|
+
GoveeApiError,
|
|
675
|
+
RateLimitError,
|
|
676
|
+
InvalidApiKeyError,
|
|
677
|
+
} from '@felixgeelhaar/govee-api-client';
|
|
678
|
+
import pino from 'pino';
|
|
679
|
+
|
|
680
|
+
class GoveeLightController {
|
|
681
|
+
private client: GoveeClient;
|
|
682
|
+
private logger = pino({ level: 'info' });
|
|
683
|
+
|
|
684
|
+
constructor(apiKey: string) {
|
|
685
|
+
this.client = new GoveeClient({
|
|
686
|
+
apiKey,
|
|
687
|
+
logger: this.logger,
|
|
688
|
+
enableRetries: true,
|
|
689
|
+
retryPolicy: 'production',
|
|
690
|
+
});
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
async initialize() {
|
|
694
|
+
try {
|
|
695
|
+
const devices = await this.client.getDevices();
|
|
696
|
+
this.logger.info(`Connected successfully. Found ${devices.length} devices.`);
|
|
697
|
+
return devices;
|
|
698
|
+
} catch (error) {
|
|
699
|
+
if (error instanceof InvalidApiKeyError) {
|
|
700
|
+
this.logger.error('Invalid API key. Please check your configuration.');
|
|
701
|
+
throw error;
|
|
702
|
+
}
|
|
703
|
+
throw error;
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
async setRoomColor(roomName: string, color: ColorRgb, brightness?: Brightness) {
|
|
708
|
+
try {
|
|
709
|
+
const device = await this.client.findDeviceByName(roomName);
|
|
710
|
+
if (!device) {
|
|
711
|
+
throw new Error(`Device not found: ${roomName}`);
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
if (!device.canControl()) {
|
|
715
|
+
throw new Error(`Device not controllable: ${roomName}`);
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
await this.client.turnOnWithColor(device.deviceId, device.model, color, brightness);
|
|
719
|
+
|
|
720
|
+
this.logger.info(`Set ${roomName} to ${color.toHex()}`);
|
|
721
|
+
} catch (error) {
|
|
722
|
+
if (error instanceof RateLimitError) {
|
|
723
|
+
this.logger.warn(`Rate limited, retrying in ${error.getRetryAfterMs()}ms`);
|
|
724
|
+
await new Promise(resolve => setTimeout(resolve, error.getRetryAfterMs()));
|
|
725
|
+
return this.setRoomColor(roomName, color, brightness);
|
|
726
|
+
}
|
|
727
|
+
throw error;
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
async getStats() {
|
|
732
|
+
return this.client.getServiceStats();
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
// Usage
|
|
737
|
+
async function main() {
|
|
738
|
+
const controller = new GoveeLightController(process.env.GOVEE_API_KEY!);
|
|
739
|
+
|
|
740
|
+
try {
|
|
741
|
+
await controller.initialize();
|
|
742
|
+
|
|
743
|
+
// Set living room to warm red
|
|
744
|
+
await controller.setRoomColor('Living Room', new ColorRgb(255, 100, 100), new Brightness(70));
|
|
745
|
+
|
|
746
|
+
// Monitor performance
|
|
747
|
+
const stats = await controller.getStats();
|
|
748
|
+
console.log('Performance:', stats);
|
|
749
|
+
} catch (error) {
|
|
750
|
+
console.error('Application error:', error.message);
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
if (require.main === module) {
|
|
755
|
+
main().catch(console.error);
|
|
756
|
+
}
|
|
757
|
+
```
|
|
758
|
+
|
|
759
|
+
## Testing Examples
|
|
760
|
+
|
|
761
|
+
```typescript
|
|
762
|
+
import { describe, it, expect, beforeEach } from 'vitest';
|
|
763
|
+
import { GoveeClient, ColorRgb, Brightness } from '@felixgeelhaar/govee-api-client';
|
|
764
|
+
|
|
765
|
+
describe('Govee Client Integration', () => {
|
|
766
|
+
let client: GoveeClient;
|
|
767
|
+
|
|
768
|
+
beforeEach(() => {
|
|
769
|
+
client = new GoveeClient({
|
|
770
|
+
apiKey: process.env.GOVEE_TEST_API_KEY!,
|
|
771
|
+
});
|
|
772
|
+
});
|
|
773
|
+
|
|
774
|
+
it('should list devices successfully', async () => {
|
|
775
|
+
const devices = await client.getDevices();
|
|
776
|
+
expect(devices).toBeInstanceOf(Array);
|
|
777
|
+
expect(devices.length).toBeGreaterThan(0);
|
|
778
|
+
});
|
|
779
|
+
|
|
780
|
+
it('should control device color', async () => {
|
|
781
|
+
const devices = await client.getControllableDevices();
|
|
782
|
+
const device = devices[0];
|
|
783
|
+
|
|
784
|
+
if (device && device.supportsCommand('color')) {
|
|
785
|
+
const red = new ColorRgb(255, 0, 0);
|
|
786
|
+
await client.setColor(device.deviceId, device.model, red);
|
|
787
|
+
|
|
788
|
+
// Verify state change
|
|
789
|
+
const state = await client.getDeviceState(device.deviceId, device.model);
|
|
790
|
+
const currentColor = state.getColor();
|
|
791
|
+
|
|
792
|
+
expect(currentColor).toBeDefined();
|
|
793
|
+
expect(currentColor!.r).toBe(255);
|
|
794
|
+
expect(currentColor!.g).toBe(0);
|
|
795
|
+
expect(currentColor!.b).toBe(0);
|
|
796
|
+
}
|
|
797
|
+
});
|
|
798
|
+
});
|
|
799
|
+
```
|